Skip to main content
GameDev.net gamedev.net

PRO Tired of ads? Read GameDev.net ad-free and help keep the community independent with GameDev Pro — $3/month.

Code snippet for typesafe void* userdata

Code snippet for typesafe void* userdata

Servant of the Lord
Servant of the Lord
Pixel ? Tile ? World · · 2 min read
2,495 4
Just tossed this piece of code together - it wraps void* points with asserts() to ensure you are getting the same type that you originally set.
Normally I shy away from void* pointers as I feel like they are indicators of bad design, but sometimes you need to use them, and when you do, they might as well be type-safe.

This is kinda a lightweight alternative to boost::any, but not designed by a peer-reviewed team of professionals who also have seats on the C++ standards committee. laugh.png

Requires C++11's typeid(), and RTTI enabled.#include #include #include //Quick macro to replace my real Assert() function, just for this file.#define Assert(condition, message) assert(condition && message) //Holds any type of user-given pointer that can be cast to void, and does run-time checks on the type of the data when retrieving it.//Ownership is *not* taken. It is the original owner's responsibillity to ensure the lifetime of the object when//someone tries to retrieve the pointer later. The Userdata class itself never de-references the pointer.class Userdata{public: Userdata() = default; template Userdata(UserType *data) { this->Set(data); } //Sets a user-spe template void Set(UserType *data) { //Save the pointer (cast to void*), and the type's RTTI information, so we can validate on retrieval. this->type_info = &typeid(UserType); this->userdata = data; } template UserType *Get() { //Validate that we've actually set *anything*. Assert(this->type_info, "We never set any data, so we can't retrieve any."); //Validate that we are getting the same type that we initially set. Assert((*this->type_info) == typeid(UserType), "The types don't match - we can't retrieve the userdata safely."); return static_cast(this->userdata); } void Reset() { this->type_info = nullptr; this->userdata = nullptr; } private: //A pointer to the global compiler-specific (but standardized) type_info for this type (the type that Userdata's void* points to). //C++11 guarantees that "The lifetime of the object referred to by [typeid()'s return value] extends to the end of the program." (N3337 5.2.8) const std::type_info *type_info = nullptr; //A pointer to the user-defined data. void *userdata = nullptr;}; //Manages a FILO stack of void pointers in a type-safe way.//This class does not take any ownership of the data pointed to.class UserdataStack{public: UserdataStack() = default; ~UserdataStack() = default; template void Push(UserType *userdata) { this->stack.push_back(Userdata(userdata)); } template UserType *Get() { Assert(!this->stack.empty(), "The stack is empty - we can't retrieve any userdata."); return this->stack.back().Get(); } void Pop() { if(!this->stack.empty()) { this->stack.pop_back(); } } //Pops the stack, and returns the pointer previously pointed to. template UserType *Take() { UserType *data = this->Get(); this->Pop(); return data; } void Clear() { this->stack.clear(); } private: std::vector stack;};

Compilable code on Ideone.com



Note: By design, the Userdata/UserdataStack classes don't take ownership of the data. I guess that's another difference than with boost::any - though you could always just pass boost::any some pointers.

Discussion

Loading comments...