Jump to content

Recent Entries

Recent Comments

Latest Visitors

- - - - -

Code snippet for typesafe void* userdata


C++
2: Adsense

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. Posted Image

Requires C++11's typeid(), and RTTI enabled.
#include <typeinfo>
#include <vector>
#include <cassert>
 
//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<typename UserType>
        Userdata(UserType *data)
        {
                this->Set(data);
        }
        
        //Sets a user-spe
        template<typename UserType>
        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<typename UserType>
        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<UserType*>(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<typename UserType>
        void Push(UserType *userdata)
        {
                this->stack.push_back(Userdata(userdata));
        }
        
        template<typename UserType>
        UserType *Get()
        {
                Assert(!this->stack.empty(), "The stack is empty - we can't retrieve any userdata.");
                return this->stack.back().Get<UserType>();
        }
        
        void Pop()
        {
                if(!this->stack.empty())
                {
                        this->stack.pop_back();
                }
        }
    
    //Pops the stack, and returns the pointer previously pointed to.
    template<typename UserType>
        UserType *Take()
        {
                UserType *data = this->Get<UserType>();
                this->Pop();
                
                return data;
        }
        
        void Clear()
        {
                this->stack.clear();
        }
        
private:
        std::vector<Userdata> 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.

Jul 29 2013 12:10 PM

Maybe a sweet snippet?

Jul 29 2013 01:17 PM

Done. I can't see any way to get to 'Sweet snippets' from the main site without following that link you just posted. Is it not integrated into the rest of the site yet? The dates on the test submissions are from 8 months ago.

Jul 30 2013 07:12 PM

It was Mike's project and to be honest I'm not sure where it's at. He hasn't been asking me to push them at all so... just looked at the page and yea still seems to have a few bugs in it. I'll let him know about it.

Jul 30 2013 08:41 PM

Well, if it's not ready or low priority, I could just toss it into an article instead, though it'd just be a copy+paste of the code, not a real article.

Note: GameDev.net moderates comments.