Copying derivated object from base class reference

Apr 15, 2010 at 4:56pm
Hello all. I'm new in this forum and was really useful for me to solve problems with C++. Thanks for that.

Now I have a problem I can't solve yet.

I'm working on a particle system class set. I have Particle and ParticleManager with a list of references to Particle objects.

1
2
3
4
5
6
7
8
class Particle
{
    friend class ParticleManager;
public:
    Particle();
    ParticleManager *getManager() {return manager;};
    ParticleManager *manager;
};



1
2
3
4
5
6
7
8
class ParticleManager
{
public:
    ParticleManager();
    particlePointerList *getParticleList() {return &particleList;};
private:
    particlePointerList particleList; //list of pointers to Particle
};


In order to properly update the state of all particles in system, I need ParticleManager to make a copy of all of them to a Particle buffer. That is easy. The problem comes becose I want Particle class to be derivable but without derivating ParticleManager too or adding/modifiying methods on derivaded class.

For example:

1
2
3
class AtractorParticle : public Particle
{
};


So I want ParticleManager to be able to copy AtractorParticle objects using only references to Particle and not adding more methods to AtractorParticle. The management of different kinds of particles has to be transparent for the user of the classes.

Is there any way to make a "blind" copy of objects without knowing their class or something?

Thank you very much.
Last edited on Apr 15, 2010 at 4:58pm
Apr 15, 2010 at 10:23pm
You need the clone pattern! Take a look at this.
http://cplusplus.com/forum/articles/18757/
Apr 16, 2010 at 12:47pm
Thank you Kempo, but that solution requires to redefine the clone method in the derived class.

I found a solution, but I think it is a bit dirty.

I give the size of the particle class to the constructor of ParticleManager so I can memcopy the particles to the buffer.

What about this code then? May it give me problems with memory or something?

1
2
3
4
5
6
7
8
9
10
11
Particle *p;
void *particleBuffer;

p = new Particle();
particleBuffer = malloc( sizeof(Particle) );

memcpy( particleBuffer , (void *) p , sizeof(Particle) );

p->update(); //I change the state of p

memcpy( (void *) p , particleBuffer , sizeof(Particle) ); //I return to the previous state 


Thanks for you colaboration.
Apr 16, 2010 at 12:57pm
You want to store a pointer to the ParticleManager in EVERY particle? Like.. if you have rain with 1,000,000 rain-drops, you want 1,000,000 copies of the same pointer all over your place? Really? ;-)


For your problem with the ParticleManager, you should consider making the manager a template taking the particle class as template argument. Then, to "create" a particle, just construct one. To copy, use the normal copy constructor.

Disadvantage will be, that one particle manager object can't hold two particle systems with different particle types in one instance anymore.

Ciao, Imi.
PS: You solution stinks. :) You shouldn't refer to memcpy over existing objects if you really don't have a very good reason for this. If you insist on your copying, at least make sure you don't have any virtual functions in your particles. (And don't even think the word "virtual inheritance").
Apr 16, 2010 at 1:56pm
You want to store a pointer to the ParticleManager in EVERY particle? Like.. if you have rain with 1,000,000 rain-drops, you want 1,000,000 copies of the same pointer all over your place? Really? ;-)

You are right, but I need particles to have acces to all other particles in the system and ask the manager for some global system methods like averages and amount of particles.

For your problem with the ParticleManager, you should consider making the manager a template taking the particle class as template argument. Then, to "create" a particle, just construct one. To copy, use the normal copy constructor.

I think this is the best solution considering I want particles to have virtual methods. But can I call particle methods or attributes from the templated manager this way?

Thanks for your help. It is being very useful for me.
Apr 16, 2010 at 2:14pm
But can I call particle methods or attributes from the templated manager this way?

You can call them directly. The compiler will remind you when you try to call something that isn't in the template type once he tries to compile the particular instance.

This also means, that you don't get compile errors unless you actually use the particle manager.


Another, option could be that you provide a base class Particle Manager who has abstract functions for copying and creating particles and these are implemented in a derrived template that needs its template type only for calling the right constructors.

I'll sketch it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct Particle {
    virtual ~Particle() {} // you want virtual destructors when destroying objects over their base pointer!
    virtual void update();
...
};

struct SpecializedParticle : Particle {
...
};

struct BaseParticleManager
{
    virtual Particle* createParticle() = 0;
    virtual Particle* copyParticle(Particle*) = 0;
    virtual void deleteParticle(Particle*) = 0;
};

template<class T>
struct ParticleManager : BaseParticleManager
{
    virtual Particle* createParticle() { return new T; };
    virtual Particle* copyParticle(Particle* p) { return new T(p); }
    void deleteParticle(Particle* p) { delete p; }
};

...
// later in the code
ParticleManager<SpecializedParticle> myParticleManager;
...


As you can see, the ParticleManager template is only responsible for copying and constructing the particles and you can implement the "normal" functions like you are used to it in the BaseParticleManager without any hazzle that comes with developing templates. (Like they always have to be in header files, take a full recompile everytime you sneeze, bad error messages and so on).

Should you encounter a special particle that needs to be constructed differently, you can specialize the template for this struct.


The solution as I presented it here has a very big performance problem: Every single particle is created on the heap separately. You have to accept this when you want to use any virtual-function based polymorphism in your Particles*), but for a lightweight-struct like particles in a game engine, this is a big blow (not because of virtual function calls but because of memory spreading. You can compensate with custom operator new's but that's really an advanced topic).

Ciao, Imi.
*) Edit: Not necessarily true, but it will be hard and full of ugly edges if you try to allocate polymorph-behaving objects in continous memory blocks.
Last edited on Apr 16, 2010 at 2:16pm
Apr 16, 2010 at 2:20pm
Oh, one more..

You are right, but I need particles to have acces to all other particles in the system and ask the manager for some global system methods like averages and amount of particles.

You can use something known as "flyweight" pattern. Basically it just means you pass the manager as parameter to all functions who need to access it.

1
2
3
4
5
6
7
8
9
10
11
struct Particle { 
    void update(ParticleManager* pm) { /* do whatever you need, using pm */ }
...
};

struct ParticleManager {
    void updateAllParticles() {
        for(auto p = particles.begin(); p != particles.end(); ++p)
            p->update(this);
    }
};


Basically trading the storage of the pointer (=bigger RAM consume, more cache misses..) for copying a pointer to the stack on each call.

Ciao, Imi.
Last edited on Apr 16, 2010 at 2:22pm
Apr 16, 2010 at 8:34pm
You can't use malloc and memcpy with non-POD objects. It is undefined behavior. This is obviously a c++ program so you really shouldn't be using malloc at all (at least not for any of the requirements that you have described thus far).
Apr 16, 2010 at 8:36pm
Thank you Kempo, but that solution requires to redefine the clone method in the derived class.


So? It's virtual. The pattern supports exactly what you said you needed to do which is to copy an object using its base class reference. It is essentially a virtual copy constructor. You only define it in the derived particle class. The particle manager does not need to know that it even exists. Did you understand the article?
Last edited on Apr 16, 2010 at 8:41pm
Apr 17, 2010 at 10:29am
I haven't words to thank you for your help. I'm working on it. I'll feedback when I get something.
Apr 19, 2010 at 7:14am
You can't use malloc and memcpy with non-POD objects. It is undefined behavior. This is obviously a c++ program so you really shouldn't be using malloc at all (at least not for any of the requirements that you have described thus far).


He uses the buffer to copy the state of the object and restores the state later. That's ok and well defined as long as there are no virtual functions in the object. (Actually, I think it is ok even for virtual objects, but I am not 100% sure about that.)

Accessing the copied object in "particleBuffer" would be "undefined behaviour" (although in the praxis, it probably works just fine). But copying it back and forth is ok.


Still, it's highly untypical and error prone. And it should be avoided except for exceptional cases. ;)

Ciao, Imi.
Last edited on Apr 19, 2010 at 7:14am
Apr 19, 2010 at 12:46pm
memcpy cannot be used period if the class has virtual methods.

But anyway, technically, according to the standard, to be compliant, the class/struct cannot even have so much
as a user defined constructor, otherwise the code verges into undefined behavior.
Apr 19, 2010 at 2:06pm
Instances of the same class has the same v-table address, it is possible to use memcpy, why not?
Apr 20, 2010 at 6:28am
jsmith: Are you sure about this? We are not talking about copying raw memory and excpecting a fully constructed object at the target memory address. Instead, we are talking about copying an already fully constructed object somewhere else and copying it back later.

This would only*) require that the memory layout of a class is not changing and AFAIK at least the size of any type is not allowed to change during runtime (it is a compile time constant).

*) Edit: On second thought, there are other requirements then just "size doesn't change". E.g. the memory address where the vtable is stored must be writeable. ;-)


boolivar: Compilers are not required to use a v-table at all. I was told that some compiler put direct function pointers when there is only one virtual function but to be honest, I never saw such a compiler in real life. (Of course, even then the function pointer would be constant too, but the point here is: The standard doesn't say anything about this, so theoretically it's undefined behaviour, even if it works in real life).

Ciao, Imi.
Last edited on Apr 20, 2010 at 6:30am
Apr 20, 2010 at 3:15pm
memcpying classes is a bad idea in any situation. Too much room for problems, and the problems would be really hard to detect.

A class which has pointers to members of itself, or a class which manages memory, for example, would both explode if memcpy'd.
Apr 22, 2010 at 7:27am
The problem with memcpy is that, undefinition. There is no standard about how compiler has to store an object in memory, so you don't know what can happen.
Actually I didn't get any error using memcpy, but I can't trust it won't happen in future or in other compiler (I'm using QtCreator).

Recently I'm checking a clone version of the particle system. If it works as I think, it can solve my problem.

Thank you.
Apr 27, 2010 at 8:05am
Well, I have almost finished the core classes of my particle system. Finally I took the clone pattern solution after having tried others too:

Template solution:
Pros:
-Don't have to redefine any method in derived particle classes.
-Homogeneous treatment of Particles.

Cons:
-Only one class of particle for every manager.
-Code is not clear to read.


Memcpy solution:
Pros:
-Don't have to redefine any method in derived particle classes.
-Homogeneous treatment of Particles.
-Various kind of particles in the same manager.

Cons:
-Need to specify to manager the highest size of the particle classes involved on it.
-Undefined instance storage so it can derivate to unexpected crashes.


Clone solution:
Pros:
-Homogeneous treatment of Particles.
-Various kind of particles in the same manager.

Cons:
-Need to redefine the clone method in derived particle classes.


I just can thank all for your help. I couln't have made it without it.
Topic archived. No new replies allowed.