Reputation: 55
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?
Upvotes: 2
Views: 2638
Reputation: 7802
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); }
};
Upvotes: 0
Reputation: 4435
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.
Upvotes: 2
Reputation: 551
Yes. The memory footprint of Derived is larger than the memory footprint for Base so the copy will not work as intended.
Upvotes: 0
Reputation: 62323
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;
}
Upvotes: 0
Reputation: 11213
YES! You're right. It wouldn't work. Because Derived1 is larger than Base, so allocating for Base is not enough memory.
Upvotes: 1
Reputation: 116256
Exactly. This is a variation of the slicing problem.
Upvotes: 4