-2

I have a problem related to designing derived classes with array parameters. I have class B derived from A. And class BB derived from AA with array of B and A respectively...

#include <iostream>

class A
{
public:
    A(){}
    virtual void foo(){std::cout<<"foo A\n";}
    int idx[3];
};

class B: public A
{
public:
    B():A(){}
    void foo(){std::cout<<"foo B\n";}
    int uidx[3];
};

class AA
{
public:
    AA(){}
    AA(int count){
        m_count = count;
        m_a = new A[count];
    }
    virtual A* getA(){return m_a;}
    ~AA(){ delete[] m_a;}
protected:
    A* m_a;
    int m_count;
};

class BB: public AA
{
public:
    BB(int count):AA()
    {
        m_count = count;
        m_a = new B[count];
    }
    B* getA(){return dynamic_cast<B*>(m_a);}
};

int main()
{
    AA* aa = new AA(2);
    BB* bb = new BB(2);
    B* b = bb->getA();
    B& b0 = *b;
    b0.idx[0] = 0;
    b0.idx[1] = 1;
    b0.idx[2] = 2;

    B& b1 = *(b+1);
    b1.idx[0] = 2;
    b1.idx[1] = 3;
    b1.idx[2] = 4;

    std::cout<<bb->getA()[1].idx[0]<<"\n"; //prints 2
    std::cout<<bb->getA()[1].idx[1]<<"\n"; //prints 3
    std::cout<<bb->getA()[1].idx[2]<<"\n"; //prints 4

    AA* cc = static_cast<AA*>(bb);
    cc->getA()[0].foo();  //prints foo B

    std::cout<<cc->getA()[1].idx[0]<<"\n"; //prints 4198624 ??
    std::cout<<cc->getA()[1].idx[1]<<"\n"; //prints 0 ??
    std::cout<<cc->getA()[1].idx[2]<<"\n"; //prints 2 ??

    cc->getA()[1].foo();  //segmentation fault
    delete aa;
    delete bb;
    return 0;
}

After static cast BB to AA I can't access A's with indices more then 0. How to solve this issue? Thank you.

  • I suggest you read the C++ FAQ section about [Is an array of Derived a kind-of array of Base](https://isocpp.org/wiki/faq/proper-inheritance#array-derived-vs-base). – jotik May 05 '16 at 13:54
  • This could be called an [array decay bug](http://stackoverflow.com/a/37052920/3919155). – jotik May 12 '16 at 07:40

2 Answers2

1

Note that cc->getA() is semantically equal to cc->A::getA() (not cc->B::getA()) and returns a pointer to A (instead of B*).

Now, since A is the subclass of B, but the latter also includes some extra fields, then sizeof(B) > sizeof(A). Since cc->getA()[n] is basically *(cc->getA() + n) the line

cc->getA()[1].foo();

does the same thing as:

A * const tmp = cc->getA();
A & tmp2 = *(tmp + 1); // sizeof(A) bytes past tmp
tmp2.foo();

which causes undefined behaviour due to §5.7.6 [expr.add] of the C++ standard which states:

For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar ([conv.qual]), the behavior is undefined. [ Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. — end note ]

You probably wanted behaviour similar to the following:

A * const tmp = cc->getA();
A & tmp2 = *(static_cast<B *>(tmp) + 1); // sizeof(B) bytes past tmp
tmp2.foo();

For that you need to use something like:

std::cout<<static_cast<B*>(cc->getA())[1].idx[0]<<"\n"; // prints 2
std::cout<<static_cast<B*>(cc->getA())[1].idx[1]<<"\n"; // prints 3
std::cout<<static_cast<B*>(cc->getA())[1].idx[2]<<"\n"; // prints 4

static_cast<B*>(cc->getA())[1].foo();  // prints foo B

However, it is better to implement a virtual A & operator[](std::size_t) operator for AA and override it in BB.

jotik
  • 17,044
  • 13
  • 58
  • 123
0

I can see 2 issues in your code:

  1. Since your classes are responsible for memory management, I would suggest to make your destructors virtual, because if you, at any point, will try to delete derived class object via base pointer, the destructors of derived classes will not be invoked. It shouldn't be a problem in your current code, but may become a problem in a future.

I.e:

int main ()
    {
    AA* aa = new BB (2);
    delete aa;
    }

Will not call the BB::~BB() in your case.

  1. The problem that you are noticing, and writing this question about.

After you cast your variable of type from BB* to AA* (even though, the cast isn't necessary, you can straight-up assign, due to types being covariant) in line:

AA* cc = dynamic_cast<AA*>(bb);

Your variable cc is treated as if it is of type AA* (it doesn't matter that it has the runtime type of BB*, in general case - you don't know, and should not care about the exact runtime type). On any virtual method call, they are dispatched to the correct type via the use of the vtable.

And now, why are you getting strange values printed in the console/segmentation fault? What's the result of cc->getA ()? Since the variable cc is treated as AA*, the return value is A* (as explained above, actual type is B*, but, due to is-a relationship of inheritance is treated as A*). What's the problem, you may ask: The array m_a is the same size in both cases, right?

Well, not really, to explain that, I would need to explain how array indexing works in C++, and how it is related to sizes of the objects.

I guess, that I wouldn't shock you, stating that size of object of type B (sizeof (B)), is larger than that of type A (sizeof (A)), since B has everything that A has (due to inheritance), with some stuff of its own. On my machine sizeof(A) = 16 bytes, and sizeof(B) = 28 bytes.

So, when you create an array, the total amount of space that array takes up is [element_count] * [size of the element] bytes, which seems logical. But, when you need to take an element from an array, it needs to figure, where exactly, that element is, in the memory, in all the space that array is taking up, so it does so, by calculating it. It does so as follows: [start of the array] + [index] * [size of element].

And, now we arrive at the source of the problem. You are trying to do cc->getA ()[1], but, since cc, under the hood, is BB*, so the size of AA::m_a variable is 2 * sizeof (B) (= 2 * 28 = 56 on my machine; first objects starts at offset 0 (0 * sizeof (B); second at offset 28 (1 * sizeof(B))), but since cc->getA () gets treated as A*, and you are trying to fetch second element from the array (index 1), it tries to fetch the object from the offset of1 * sizeof (A)`, which, unfortunately, is in the middle of the space reserved to an object, and yet, any values can be printed/anything can happen - undefined behavior is invoked.

How to fix it? I would fix it by implementing the virtual indexing operators, instead of GetA method on classes AA/BB, as follows:

class AA
    {
    public:
        ...
        virtual A& operator[] (int idx)
            {
            return m_a[idx];
            }
        ...
    };

class BB : public AA
    {
    public:
        ...
        virtual B& operator[] (int idx)
            {
            return dynamic_cast<B*>(m_a)[idx];
            }
        ...
    };

But, then you would need to be careful to call the operator on the object itself, and not to a pointer to object:

std::cout << cc->operator[](1).idx[0] << "\n";
std::cout << cc->operator[](1).idx[1] << "\n";
std::cout << cc->operator[](1).idx[2] << "\n";
Algirdas Preidžius
  • 1,769
  • 3
  • 14
  • 17
  • Thank you for detailed response. I had similar thoughts. Referring to 1. If I add prints to AA destructor it shows me that it's being called on deleting bb without even marking it as virtual. As to 2. Is there any other ways to solve it? I have a side API and I wanted to add some overrides like B and BB without modifying most of API if it's possible. – user3147006 May 05 '16 at 13:19
  • @Oh, yeah, that's right. I got the ordering of destructor invocations mixed up. Will fix that momentarily. Yes, there is another way in solving it, but is, in a sense, the same as what jotik suggested, but with the usage of `dynamic_cast` instead of `static_cast`, but, in my opinion, this solution is cleaner. – Algirdas Preidžius May 05 '16 at 13:33