views:

135

answers:

6

Say I have these structs:

struct Base{
 ...
}

struct Derived:public Base{
 //everything Base contains and some more
}

I have a function in which I want to duplicate an array of these and then alter it.

void doStuff(Base *data, unsigned int numItems){
 Base *newdata = new Base[numItems];
 memcpy(newdata, data, numItems*sizeof(Base));
 ...
 delete [] newdata;
}

But if I used this function like so:

Base *data = new Derived[100];
doStuff(data, 100);

It wouldn't work, would it? Because Derived1 is larger than Base, so allocating for Base is not enough memory?

+1  A: 

YES! You're right. It wouldn't work. Because Derived1 is larger than Base, so allocating for Base is not enough memory.

anthares
+4  A: 

Exactly. This is a variation of the slicing problem.

Péter Török
A: 

You could do this easily with a template:

template< class T >void doStuff(T *data, unsigned int numItems)
{
    T *newdata = new T[numItems];
    memcpy( newdata, data, sizeof( T ) * numItems );
    ...
    delete [] newdata;
}

Edit as per the comments: If you wanted to do this for a mixed collection things will get more complicated quickly ... one possible solution is this:

struct Base{
    virtual Base* CopyTo()      { return new Base( *this ); }
};

struct Derived:public Base{
    virtual Derived* CopyTo()   { return new Derived( *this ); }

};

void doStuff( Base** ppArray, int numItems )
{
    Base** ppNewArray   = new Base*[numItems];
    int count = 0;
    while( count < numItems )
    {
        ppNewArray[count] = ppArray[count]->CopyTo();
        count++;
    }

    // do stuff

    count = 0;
    while( count < numItems )
    {
        delete ppNewArray[count];
        count++;
    }
    delete[] ppNewArray;
}
Goz
That seems OK, but wouldn't work if you wanted a collection of mixed types of these things. But maybe the OP isn't doing a mixed collection?
Kevin
No it wouldn't work with a mixed collection. You'd be hard pushed to define an array of collected items as per the OP's example. You'd need pointers to pointers. At that point you'd need to use some form of virtual assignment function to perform the correct copy.
Goz
+2  A: 

You'll need to use pointers and use copy constructors. Oh and also, don't use the keyword struct for more than basic data structures. Technically it works, but what you're creating is class hierarchy, so use the class keyword.

This won't work simply because Derived is bigger and also is for intents and purposes a completely different object that is compatible with Base mainly by interface, but more importantly, when dealing with classes, you shouldn't really use low-level memory manipulation. Instead, you should be setting up copy constructors and use libraries like < algorithm > to perform templated actions on them.

Further more, the reason why it won't work, despite being legal syntax (i.e. Base * = Derived *), is that your allocating larger objects than what a Base * would index into, which would lead to memory corruption by writing memory to the wrong location.

For example, if a Base object is 4 bytes, C++ would index the array every four bytes, but if the actual allocated Derived objects are 8 bytes then you're indexing halfway across object boundaries and your member variables won't be pointing to the right location in memory.

Using class hierarchies in an array:

Base *objects[100];
for (int i = 0; i < 100; i++)
    objects[i] = new Derived();

Even further, to make things easier to manage, you may want to use a smart pointer mechanism and a template list instead of raw pointers.

Nick Bedford
+1 for the correct OOP way to do this. It's also important to keep in mind that the original `Base *data = new Derived[100];` may be legal syntax, but doesn't make sense from a polymorphic standpoint (arrays are not always the same thing as pointers).
JonM
@JonM you're right. For example, essentially its allocating 100 8 byte objects when the compiler would index them as 4 byte objects which will lead to memory corruption. The second Base index would actually be the second half of the first.
Nick Bedford
A: 

Yes. The memory footprint of Derived is larger than the memory footprint for Base so the copy will not work as intended.

Jeremy Simon
A: 

Well, an array of Derived is not an array of Base.

If you need to upcast a Derived* to a Base*, you should allocate an array of pointers to Base or, preferably, a vector<Base*>

vector<Base*> data(100);
// Initialize the elements
for (vector<Base*>::iterator it = data.begin(); it != data.end(); ++it)
{
    *it = new Derived;
}

doStuff(data);

// Destroy the elements
for (vector<Base*>::iterator it = data.begin(); it != data.end(); ++it)
{
    delete *it;
}

And your doStuff function becomes:

void doStuff(const vector<Base*>& data)
{
    // Copy the objects, not the pointers
    vector<Base*> newdata;
    for (vector<Base*>::const_iterator it = data.begin();
         it != data.end(); ++it)
    {
        newdata.push_back((*it)->clone());
    }

    // Do stuff

    // Destroy the copies
    for (vector<Base*>::iterator it = newdata.begin();
         it != newdata.end(); ++it)
    {
        delete *it;
    }
}

Note that, to copy the objects without knowing whether they are Base or Derived, we need to use the virtual constructor idiom. It requires modifying Base and Derived like this:

struct Base{
    ...
    virtual Base* clone() const { return new Base(*this); }
    virtual ~Base() {}
};

struct Derived : public Base {
    ...
    Derived* clone() const { return new Derived(*this); }
};
Danilo Piazzalunga