• [C/C++] Initializing the data pointed to by a void* pointer to another void* pointer's
    8 replies, posted
[b]ANSWERED BELOW.[/b] I'm trying to make a class that takes a void* pointer to user-defined data, allocs a new block of data with the same size as the user-defined data, and then sets the value of the newly alloc'd data to the same value as the data passed by the user. I want this to happen regardless of the type of data because otherwise I would have to setup an enumeration or something to specify what types of data the user can pass to the class. Is there any way to do this? (failed attempt below) [code]#include <cstdlib> #include <iostream> class Test { public : void* GetUserData() { return m_UserData; } void SetUserData(void* UserData, size_t Size) { // Allocate a block with the same size as the user-defined data m_UserData = std::realloc(m_UserData, Size); // Now I need to make the newly allocated *m_UserData equal *UserData before *UserData goes out of scope; // so that I can still retain the value of *UserData afterwards // // My theory here was that if you make both the void pointers the same type, then make the data they point // to equal, they should have the same bits and thus if casted back to the original type would be the same // // The reason a work-around like this became necessary is because you can't make two void pointer's data equal *reinterpret_cast<char*>(m_UserData) = *reinterpret_cast<char*>(UserData); } private : void* m_UserData; }; int main() { // Data is a struct that represents generic user-defined data struct Data { int x; int y; }; Test MyTest; // This is in a seperate scope because the class needs to be able to handle this { // The generic user-defined data Data MyData; MyData.x = 431; MyData.y = 5315; // Feed the user-defined data to the Test class MyTest.SetUserData(&MyData, sizeof(MyData)); } // After m_UserData equals MyData, the user is in control again and he can use the data with knowledge of it's type Data* c = static_cast<Data*>(MyTest.GetUserData()); std::cout << c->x << ' ' << c->y << std::endl; } [/code] I hope somebody understands this stuff. At least tell me that it's impossible to ease my mind.
[QUOTE=Matthew0505;26135436]You can't realloc stack variables, or at least you shouldn't[/QUOTE] Good to know, I just shoved that code up real fast trying to get a minimal representation of what I was trying to do. I mainly code in C++ using new and delete so I don't run into that problem. I only used realloc here because that is what the API I am using does. I thought of an easier way to phrase the OP, "How do I clone void* data without knowing it's type?"
That std::realloc() call looks OK, actually, as long as you initialize m_UserData to 0 so that the first realloc call acts like a malloc. The problem is in the way you're trying to copy the data: you're casting to char* but that assignment only copies one character, not all the data. It's also an ugly kluge. You can get what you want with std::memcpy(), though. However, this sort of type-unsafe void* trickery is generally not a good idea. In particular, since you don't know the type of the user data, you don't know whether it has a copy constructor that ought to be called; just duplicating all its bytes with memcpy() may not be the correct way to copy it. There's probably a better way to accomplish whatever you're trying to accomplish. For example, you could define an abstract base class with pure-virtual functions for whatever operations need to be done with the user-defined data, and the user could pass in a pointer to an instance of a derived class that holds whatever private data it needs. Or you could make your class a template, so that it can work directly with arbitrary data types without sacrificing type-safety. [QUOTE=WiZzArD;26135494]I mainly code in C++ using new and delete[/QUOTE] You should look into "smart pointer" classes, such as std::auto_ptr and [url=http://www.boost.org/doc/libs/release/libs/smart_ptr/smart_ptr.htm]boost::shared_ptr[/url] (also known as std::tr1::shared_ptr if your compiler supports it). Manually deleting things is error-prone and tends to leave you with memory leaks. In particular, it's tricky to ensure that memory allocated for temporary use within a function gets freed if the function aborts prematurely due to an exception. One very good way to do it is to wrap the pointer in an object whose destructor deletes it when the object goes out of scope — an idiom called [url=http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization]RAII[/url] — and that's exactly what smart pointers do.
[cpp]#include <cstdlib> #include <iostream> class Test { public : Test():m_UserData(0){} ~Test(){ delete m_UserData; } template<typename T> T* GetUserData() { return reinterpret_cast<T*>(m_UserData); } //note: the userdata will be copied, not stored as reference template<typename T> void SetUserData(const T &UserData) { // Allocate new memory and copy the data m_UserData = reinterpret_cast<void*>(new T(UserData)); } void RemoveUserData() { delete m_UserData; m_UserData = 0; } private : void* m_UserData; }; // Data is a struct that represents generic user-defined data struct Data { int x; int y; }; int main() { Test MyTest; // This is in a seperate scope because the class needs to be able to handle this { // The generic user-defined data Data MyData; MyData.x = 431; MyData.y = 5315; // Feed the user-defined data to the Test class MyTest.SetUserData(MyData); } // After m_UserData equals MyData, the user is in control again and he can use the data with knowledge of it's type Data *c = MyTest.GetUserData<Data>(); std::cout << c->x << ' ' << c->y << std::endl; }[/cpp] If the user must set a UserData, then you could also return a reference in GetUserData. Or you could do it like [cpp]template<typename T> void GetUserData(T &UserData) { if(m_UserData) UserData = *reinterpret_cast<T*>(m_UserData); }[/cpp] - the same would work with pointers - [cpp]template<typename T> void GetUserData(T *&UserData) { UserData = reinterpret_cast<T*>(m_UserData); } //.... Test *c; myTest.GetUserData(c); if(!c) ; //no userdata available else ; //userdata available[/cpp] Or you could also use a templated class: [cpp]template<typename UserDataType> class Test { //... private : UserDataType *userData; };[/cpp] Which would then also include type safety, but depending on how you want to use it may not be possible.
Zeeky, I saw templates in your first code snippet and thought you had the right idea, then I saw reinterpret_cast and facepalmed. The problems with doing it that way are: [list] [*]Users can call SetUserData<Fred>(fred) and then GetUserData<Barney>(), which makes no sense. [*]The "delete m_UserData" won't run its destructor because there's no type information to indicate what destructor should be run. [/list] [QUOTE=ZeekyHBomb;26135737]Or you could also use a templated class:[/QUOTE] Yes. Do that.
std::memcpy() at a glance looks like exactly what I want. The template suggestion doesn't quite suit my taste, but may actually be a nice way to do it. I've never liked having templates in my own code. The reason I am not using inherited classes is because the user data shares no common variables or functions. The class that owns it does not know what it is. It just puts the data into a variable for the scripting language of the application. The scripting API then associates a private key to the generic data and only reveals that key when a method of the variable is called, thus allowing me to cast to the data's real type based on the key.
[QUOTE=Wyzard;26135783]Zeeky, I saw templates in your first code snippet and thought you had the right idea, then I saw reinterpret_cast and facepalmed. The problems with doing it that way are: [list] [*]Users can call SetUserData<Fred>(fred) and then GetUserData<Barney>(), which makes no sense. [*]The "delete m_UserData" won't run its destructor because there's no type information to indicate what destructor should be run. [/list] Yes. Do that.[/QUOTE] Well, sometimes there is no ideal solution and you have to trust the programmer. You could, for debug-builds (or also release-builds if you don't care) store some RTTI (like std::type_info). Your suggestion of inheritance-based user-data, which would also need RTTI, is also not ideal, because you can only store stuff that inherits from this user-data baseclass. I'm not claiming my version to be perfect, but depending on the circumstances the sacrifice is acceptable. [editline]18th November 2010[/editline] Oh, also I didn't think about the destructor not being called. You could resolve that by using a function-pointer to a helper-class function: [cpp]class Test { private : template<typename T> class Deleter { public: static void deleter(void *data){ delete reinterpret_cast<T*>(data); } }; //... void *m_UserData; void (*deleter)(void*); //is set to &Deleter<T>::deleter };[/cpp] Actually, you could probably easily wrap up typesafety like this, by comparing the member-deleter with Deleter<T>::deleter.
[QUOTE=WiZzArD;26135929]It just puts the data into a variable for the scripting language of the application. The scripting API then associates a private key to the generic data and only reveals that key when a method of the variable is called, thus allowing me to cast to the data's real type based on the key.[/QUOTE] You're taking some data from code that knows its type, storing it without knowing its type, and then giving it back later to code that does know its type. That's fine by itself, but the problem is that if you don't know something's type, you can't (safely) do any operations on it — including copying it. If you know that the data you're storing will always be POD types (int, char, etc.) then std::memcpy() is OK because those types never require anything more than byte-for-byte copying. But if you might be storing class instances, you should really strive for type safety. Otherwise it'll bite you later when you try to store something that has nontrivial copy semantics and you get subtle bugs (undefined behavior, actually) as a result. There are two basic solutions to this type of problem: you can make your class a template so that it [i]does[/i] know the type of what it's holding, or you can design your program so that the copying and deletion of the user data is the responsibility of the calling code rather than the generic script-variable-holder class. It sounds like you're envisioning a single variable holder that can hold different types of values (e.g. key 1 might be an integer while key 2 is a string), and that's tricky to get right since it pretty much requires runtime casting. But if the code that's storing and retrieving these values knows their types at compile time — and it must, if it can cast them back to the right type without the type information actually being stored anywhere — then you could have a separate variable holder for each type of data being stored, and make it a template so it can handle each value safely. On the other hand, if your scripting language is dynamically-typed, then you probably do need to store all the values in a single variable holder, and you need to store some type information so that the code which retrieves a value can determine how it should be handled. The template approach won't work in this case, but you can make an abstract ValueHolder class, with a virtual clone() function that makes a copy and returns a new ValueHolder*, and derived classes like IntValueHolder and StringValueHolder that know how to copy their respective types. Your variable holder can associate each variable key with a ValueHolder* and some type information, and the caller that retrieves a variable can use the type information to cast the ValueHolder* to the correct concrete type. [QUOTE=WiZzArD;26135929]I've never liked having templates in my own code.[/QUOTE] Templates are an essential part of modern C++, so you're hampering yourself if you avoid using them where appropriate. Look at all the standard containers, for example: they're templates, they can hold data of any type, and they do so correctly and safely. [QUOTE=ZeekyHBomb;26135999]Well, sometimes there is no ideal solution and you have to trust the programmer.[/QUOTE] That's true, but I don't think this is one of those cases.
Sorry, you need to Log In to post a reply to this thread.