-2

I am trying to write a class function that can be called to dynamically create a new derived object and add that object to a dynamically allocated array that stores pointers to objects of the base class. If I run the function twice, I can access objectAry[0], but accessing objectAry[1] gives me read access violations. Can anyone tell me why this isn't storing the objects appropriately?

The objectAry was dynamically allocated with space for 10 objects, so there is room in position 1. This occurs even when newObject() is only called twice.

//Array definition (outside of function):    
Base* objectAry;
objectAry = new Derived[10]


//Function in question:
void Derived::newObject()
{   
Derived* tempObject = NULL;
tempObject = new Derived;
objectAry[numObjects] = *tempObject;
numObjects++;
delete tempObject;
tempObject = NULL;
}

When running a simple function to return one of the derived object's member variables:

Exception thrown: read access violation.
this->objectAry+1-> was 0x1.

Clearly I'm storing this incorrectly, but I'm not sure how to get this working.

  • *but I'm not sure how to get this working* -- Use `std::vector` or `std::vector>` instead. The whole thing starts becoming a mess on the first two lines: `Base* objectAry; objectAry = new Derived[10];` – PaulMcKenzie Jul 12 '19 at 22:32
  • @PaulMcKenzie I need to implement this without using vector, unfortunately... it seems like the issue has to do with the new Derived object not allocating and storing in the array appropriately on subsequent iterations, even though the array counter is correct - would vector solve that somehow or is this a style/efficiency suggestion? – razondiestar Jul 12 '19 at 22:51
  • You already have 10 `Derived` objects. What is the purpose of that function you wrote? – PaulMcKenzie Jul 12 '19 at 22:58
  • Not enough to answer the question here, but my money is on [improper polymorphism](https://ideone.com/NG2yB3) resulting in [Object Slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing). Note the call of `Base`'s Assignment operator. Ooops. Come to think of it, I don't think I can make even this call with what's given. [mcve], please! – user4581301 Jul 12 '19 at 23:29
  • `objectAry[numObjects] = *tempObject;` will slice `*tempObject` since it is of type `Derived` but `objectAry[numObjects]` is of type `Base`. Any usage of `objectAry[numObjects]` after that will (depending to some extent on what other code is doing, although you haven't shown that) give undefined behaviour. Also, a problem that your testing has not yet shown: you're not doing bounds checking on `numObjects` to ensure you're not running past the end of the dynamically allocated array. – Peter Jul 13 '19 at 01:42

1 Answers1

0

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
HalB
  • 11
  • 3