views:

182

answers:

6

hi, i have a "data provider" which stores its output in a struct of a certain type, for instance

struct DATA_TYPE1{
   std::string data_string;
};

then this struct has to be casted into a general datatype, i thought about void * or char *, because the "intermediate" object that copies and stores it in its binary tree should be able to store many different types of such struct data.

struct BINARY_TREE_ENTRY{
   void * DATA;
   struct BINARY_TREE_ENTRY * next;
};

this void * is then later taken by another object that casts the void * back into the (struct DATA_TYPE1 *) to get the original data. so the sender and the receiver know about the datatype DATA_TYPE1 but not the copying object inbetween.

but how can the intermidiate object deep copy the contents of the different structs, when it doesn't know the datatype, only void * and it has no method to copy the real contents; dynamic_cast doesn't work for void *;

the "intermediate" object should do something like:

void store_data(void * CASTED_DATA_STRUCT){
   void * DATA_COPY = create_a_deepcopy_of(CASTED_DATA_STRUCT);
   push_into_bintree(DATA_COPY);
}

a simple solution would be that the sending object doesn't delete the sent data struct, til the receiving object got it, but the sending objects are dynamically created and deleted, before the receiver got the data from the intermediate object, for asynchronous communication, therefore i want to copy it.

instead of converting it to void * i also tried converting to a superclass pointer of which the intermediate copying object knows about, and which is inherited by all the different datatypes of the structs:

struct DATA_BASE_OBJECT{
public:
    DATA_BASE_OBJECT(){}
DATA_BASE_OBJECT(DATA_BASE_OBJECT * old_ptr){
         std::cout << "this should be automatically overridden!" << std::endl;
    }
    virtual ~DATA_BASE_OBJECT(){}
};

struct DATA_TYPE1 : public DATA_BASE_OBJECT {
public:
    string str;
    DATA_TYPE1(){}
    ~DATA_TYPE1(){}
    DATA_TYPE1(DATA_TYPE1 * old_ptr){
         str = old_ptr->str;
    }
};

and the corresponding binary tree entry would then be:

struct BINARY_TREE_ENTRY{
       struct DATA_BASE_OBJECT * DATA;
       struct BINARY_TREE_ENTRY * next;
    };

and to then copy the unknown datatype, i tried in the class that just gets the unknown datatype as a struct DATA_BASE_OBJECT * (before it was the void *):

void * copy_data(DATA_BASE_OBJECT * data_that_i_get_in_the_sub_struct){
     struct DATA_BASE_OBJECT * copy_sub = new DATA_BASE_OBJECT(data_that_i_get_in_the_sub_struct);
     push_into_bintree(copy_sub);
}

i then added a copy constructor to the DATA_BASE_OBJECT, but if the struct DATA_TYPE1 is first casted to a DATA_BASE_OBJECT and then copied, the included sub object DATA_TYPE1 is not also copied.

i then thought what about finding out the size of the actual object to copy and then just memcopy it, but the bytes are not stored in one row and how do i find out the real size in memory of the struct DATA_TYPE1 which holds a std::string?

Which other c++ methods are available to deepcopy an unknown datatype (and to maybe get the datatype information somehow else during runtime)

thanks Ewald

+9  A: 

If you have a void * there is no way of extracting any type information from it. That is why void * are very, very rarely used in C++ programs (I honestly cannot remember the last time I used one) - your approach here is completely mistaken. If you want generic containers where the type is known at compile time, use templates. If you want containers where the type varies at run-time, derive from a base class and use containers of base class pointers. And don't write your own containers (except possibly as learning exercises ) - C++ has a perfectly good binary tree implemented as std::set and std::map.

And lastly, don't use ALL CAPS for names of C++ types.

anon
+1 for beating me to it by 19 secs.
Cam
To emphasize what Neil added about the std: Good use of the std is vital if you want to properly code in C++. It seems as though you are coding in a very C-like manner (for example your use of void pointers), which isn't generally accepted as good C++. You should look into the stl and also look into templates.
Cam
+1 for discouraging ALL CAPS. My eyes! :-D
DevSolar
@DevSolar - haha I thought exactly the same thing: `struct DATA_TYPE1` :)
Cam
"I honestly cannot remember the last time I used one" Typical example is passing user data pointer into OS API function, so the data can be accessed during callback.
SigTerm
@Sig Term: Seems like the OS API function is a C binding then > wrap into C++ and forget you ever saw it.
Matthieu M.
@Matthieu M.: Even if you wrap it you'll still be passing void* in C++ code. Besides, sometimes wrapping isn't worth the effort.
SigTerm
@Sig Term: if you wrap it you'll only write the conversion code once (T* -> void* -> T*), and once debugged you won't have to either worry about it. As for using void* in C++: it's because you're interacting with a C API and thus write C code interpreted by a C++ compiler.
Matthieu M.
A: 

Void pointers are not a good solution here because they do not guarantee type-safety at compiletime.

I suggest using templates. This will allow you to still deal with different datatypes using the same class/functions, and will also guarantee typesafety much better than void pointers.

Edit: To further clarify why void pointers even exist: void pointers were used for this kind of thing in C. However while you can still use them in C++, it is usually discouraged because there are better solutions.

Also, you mention deepcopying. All types to be used with the database should either have to implement deepCopy function (this is an OOP-style approach), or you could simply remember to overload the assignment operator for all types you use with your DATA_BASE :)

Cam
A: 

but how can the intermidiate object deep copy the contents of the different structs, when it doesn't know the datatype, only void * and it has no method to copy the real contents; dynamic_cast doesn't work for void *;

You simply can't do that. A void* block representing certain data is bound to contain multiple pointers (std::string allocates memory dynamically, for example). And you won't know where exactly they are stored, so there will be no way to deep copy data without causing a mess somewhere.

dynamic_cast doesn't work for void *;

You could try to cast void* into some base type, then dynamic_cast into whatever you want. However, I can't guarantee that it will be safe to use that with object created by multiple inheritance, for example. It will be safer to use some abstract class for exchanging data, instead of void* pointers.

SigTerm
A: 

Which other c++ methods are available to deepcopy an unknown datatype (and to maybe get the datatype information somehow else during runtime)

What you're looking at here is serialization: The ability to place various objects in a binary stream, move the stream around - and maybe store it, then get a copy of the original contents back out of the stream.

You can implement this yourself, or you can base your implementation on existing libraries.

One option I have used is boost::serialization. You basically have to implement either a serialize method in all your class hierarchy, or a save and a load method in all your class hierarchy.

The code is pretty straightforward. See here for the tutorial.

utnapistim
A: 

thanks for the quick answers.

i'm somehow too lazy to rewrite the code in the future each time in every class when a new datatype is added.

for me now i could do it with templates for each datatype that is used, but i really wonder from a code development point of view if there isn't a simpler thing in c++. you are right that i come from c and i will slowly behave myself when programming in c++.

i tried this approach with inheritance of a base class, which is already a restriction for coding, because every new datatype has to be coded to inherite the base struct, but this doesn't work, because only calling the copy contructor of the base struct, which is the only thing the "copy-object" knows (because it doesn't know anything about the derived struct datetypes), can do, doesn't work.

the copy contructor of the derived struct is not called, or am i doing something wrong here? i tried it with the assignment operator, but it doesn't do a copy and as soon as the original data is deleted, it is a dangling pointer i guess.

At the moment it works like this 1.) the "sending object" creates a struct DATA_TYPE1 * with new DATA_TYPE1 ... 2.) and fills it with data 3.) and casts it to void * 4.) and tells the "copy-object" about this pointer 5.) then the "sending object" is deleted, the contents of the void * is still alive 5.) the "copy-object" stores just the void * 6.) the "receiving object" gets this void * at a later time 7.) and casts it back to the struct DATA_TYPE1 * and uses its data 8.) and finally deletes the data

so no copying is done here just handing over the pointer to the original data from one object to the other.

thanks for the advice about serialisation i thought about that, but at the moment i want to turture myself with pure c++ to solve this problem.

Ewald Peters
People normally implement a "clone" method that will mimic a virtual copy constructor, where "clone" will simply return new T(*this);.
DeadMG
A: 

can you show me how to implement this clone method? isn't there the same problem: that the "copy-object" can't use this clone method, because it doesn't know the datatype of the result. this code works, but the copying should be done in the storage class:

    class datatype1 {
    public:
        string str;
        datatype1(){}
        datatype1(datatype1 * old_ptr){
            str = old_ptr->str;
        }
        ~datatype1(){}
        datatype1 * clone(){
            datatype1 * clone_of_me = new datatype1(this);
            return clone_of_me;
        }
    };

    class storing_class{
    public:
        void * storage_ptr;
        storing_class(){}
        ~storing_class(){}
        void store_data(void * data_ptr){
            storage_ptr = data_ptr;
        }
        void * get_data(){
            return storage_ptr;
        }
    };

    class sending_class{
    public:
        sending_class(){}
        ~sending_class(){}
        void send_data(storing_class * storing_class_ptr){
            datatype1 * data = new datatype1();
            data->str = "ABC";
    // this copying should be done in the storing_class
            datatype1 * data_copy = data->clone();
// could also be done with this:
//          datatype1 * data_copy = new datatype1(data);
            storing_class_ptr->store_data((void *) data_copy);
            cout << "data sent: " << data->str << endl;
            delete(data); // to make sure the result is a copy and not pointing to here

        }
    };

    class receiving_class{
    public:
        receiving_class(){}
        ~receiving_class(){}
        void fetch_data(storing_class * storing_class_ptr){
            datatype1 * data = (datatype1 *) storing_class_ptr->get_data();
            cout << "data received: " << data->str << endl;
        }
    };

    int main(){
        sending_class * sender = new sending_class();
        storing_class * storage = new storing_class();
        sender->send_data(storage);
        delete(sender); // to make sure the result is a copy and not pointing to here
        receiving_class  * receiver = new receiving_class();
        receiver->fetch_data(storage);
        return 0;
    }
Ewald Peters