First, in reference to:
//Array definition (outside of function):
Base* objectAry;
objectAry = new Derived[10];
A pointer to Base is allocated, but assigned a const pointer to the array of class Derived. This does not cause the fault, but sets up the subsequent code to fail.
In reference to the following statement,
objectAry[numObjects] = *tempObject;
the compiler views objectAry
as an array of class Base. The sizeof(Base)
is different than sizeof(Derived)
, which leads to an error in the determining
the pointer to the index within the dynamic array of class Derived
. When the memory, within the array is modified with an improper offset, it is only a matter of time until a memory fault is generated with anything other than a trivial example.
In the following code, a context was created in a manner to reproduce the memory fault within the question. In the comments, you will see where the assignment was replaced with one that uses a dynamic_cast operation. The dynamic_cast now allows the index offsets to be correctly computed. Further, note the use of the dynamic_cast operations throughout, and the handling of the assignment operator. Caution is advised when using base pointers in this manner. See "Caution:" note at the end.
#include <iostream>
using namespace std;
class Base;
class Base{
public:
Base(){}
Base& operator=(Base& src){
copy_like_objects(this, &src);
return *this;
}
virtual void copy_like_objects(Base* dst, Base* src) = 0;
virtual ~Base(){};
};
class Derived : public Base{
public:
static const int objectAryLength = 10;
static int numObjects;
static Base* objectAry;
Derived();
~Derived(){};
static void newObject();
int data;
void copy_like_objects(Base *dst, Base *src){
*dynamic_cast<Derived*>(dst) = *dynamic_cast<Derived*>(src);
}
Derived& operator=(Derived& src){
data = src.data;
return *this;
}
static void allocate();
static void deallocate();
};
Derived :: Derived(){
data = -1;
}
void Derived :: allocate(){
if(objectAry == nullptr){
objectAry = new Derived[Derived :: objectAryLength];
}
for(int i = 0; i < Derived::objectAryLength; i++){ dynamic_cast<Derived*>(objectAry)[i].data = 0;}
}
void Derived :: deallocate(){
if(objectAry != nullptr){
delete[] dynamic_cast<Derived*>(objectAry);
objectAry = nullptr;
}
}
void Derived::newObject(){
Derived* tempObject = nullptr;
tempObject = new Derived; // The debugger may not step through
// the default constructor, which is called.
// tempObject = new Derived(); // Debugger steps through default constructor.
// At the time of this writing, in the commented statement
// the compiler seems to be computing the sizeof class base
// to evaluated the index into an array of the supposedly
// allocated array of bases classes instead of flagging as an error.
// As a result, The derived class copies data on a missaligned
// Derived object allocation, currupts the array of objects, which then
// is the cause of a subsequent memory fault.
//
// objectAry[numObjects] = *tempObject;
// Using the cast below, fixes the alignment issues and avoid a memory fault.
dynamic_cast<Derived*>(objectAry)[numObjects] = *tempObject;
numObjects++;
delete tempObject;
tempObject = nullptr;
}
int Derived::numObjects = 0;
Base* Derived::objectAry = 0;
int main(int argc, char **argv) {
Derived :: allocate();
for(int i = 0; i < Derived::objectAryLength; i++){
cout << (dynamic_cast<Derived*>(Derived::objectAry))[i].data << " : " ;
} cout << endl;
Derived::newObject();
Derived::newObject();
for(int i = 0; i < Derived::objectAryLength; i++){
cout << (dynamic_cast<Derived*>(Derived::objectAry))[i].data << " : " ;
} cout << endl;
Derived :: deallocate();
return 0;
}
Caution: In general, when base class pointers are used in this manner, one will create a situation where there is an high likelihood of making an error that causes a memory fault or an exception during a dynamic_cast<>, which can easily show up in run time edge cases. To resolve this issue, consider redesigning and start with:
Base** objectAry;
objectAry = new Base*[10];
The logic to manage the list can be placed in the Base class without knowledge of which derivations are stored within the list. This approach leverages the polymorphic nature of C++ and will simplify coding with improved reliability. This approach will allow any derived class of Base to be managed within the list. It is important to properly manage the allocation and deallocation of derived objects within the list management logic.