0

Consider following code:

#include <iostream>

using namespace std;

class Base
{
public:
    int foo;
};

class Derived : public Base
{
public:
    float bar;
};

int main()
{
    Base** baseArr = new Base*[30];

    for (int i=0; i<30; i++)
    {
        Derived* derived = new Derived();
        derived->foo = i;
        derived->bar = i * 2.5;
        baseArr[i] = derived;
    }

    //Notice this!
    Derived** derivedArr = (Derived**)(baseArr);

    for (int i=0; i<30; i++)
        cout << "My Base " << i << ": " << derivedArr[i]->foo << ", " << derivedArr[i]->bar << endl;

    return 0;
}

Is it safe to do this array to array cast? The pointer size is same across the program, so it sounds like I won't get any padding errors. However, I know that the correct way to do this is to iterate through each element and cast it individually.

However, Im trying to utilize this kind of cast to move my template public functions implementations to .cpp file by using non-generic private function returning array, so I can be sure that my Base array will contain only specific Derived pointers.

private:
    Base** Find(function<bool(Base*)> Predicate, int& N); //Implemented in .CPP

public:
    template <class T> T** FindByType(int &N) //T is derived from Base
    {
        //Safe?
        return (T**)(Find(
            [](Base* b)->bool { return typeid(T) == typeid(b); },
            N));
    };

This is just a simplified example, of course. I've got number of reasons to use RTTI in this case. N is used to controll array size.

I am wondering if this unsafe cast will fail with multi-inheritance, like for example Derived class would also inherit OtherBase and I'd like to cast to OtherBase**, I'd also like to know if by any chance I reach undefined behaviour with this cast, or any potential problems I may encounter if I decide to use this construct.

noisy cat
  • 2,865
  • 5
  • 33
  • 51

2 Answers2

2

No it is not safe.

A pointer to Derived is not the same thing as a pointer to Base. A pointer to Derived can be converted to a pointer to Base, but the end result is a different pointer.

And because a pointer to Derived is not the same thing as a pointer to Base, a pointer to a pointer to Derived is also not the same thing as a pointer to a pointer to Base.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • They share common memory size. I can be sure that my Base** array contains only Derived* objects. You can in many cases cast Base* to Derived*, especially when you are sure that the types match. – noisy cat Oct 07 '16 at 12:43
  • 1
    The standard aside, wouldn't it be safe if there was no virtual/multiple inheritance used? On modern popular architectures, anyway. – krzaq Oct 07 '16 at 12:44
  • @krzaq That's exacly what I'm talking about, I can stick to some constraints if it will allow me to speed up the code. Multiple inheritance isn't the crucial feature here. – noisy cat Oct 07 '16 at 12:47
  • 1
    Virtual or multiple inheritance makes no difference, whatsoever. Neither does it matter that the array contains only `Derived *` objects. – Sam Varshavchik Oct 07 '16 at 12:47
  • Could you please explain why? We are just operating on integer numbers while dealing with addresses. I know that this is not the 'standard way' as I mentioned in the question, however, I don't want to iterate 3000 element array only to convert the pointers, and templates are very nice to use, instead of doing it manually after recieving Base* array. – noisy cat Oct 07 '16 at 12:54
  • I already explained: the pointers are different. Setting aside some pathological cases, a pointer to `Derived` is a different memory address than a pointer to `Base`. "A different pointer" should not be a complicated concept to understand. If you forcibly make this cast, and attempt to then use the pointer, the end result is an attempt to access an object using a completely wrong memory address. Hillarity ensues. – Sam Varshavchik Oct 07 '16 at 12:56
  • http://ideone.com/hefOXw What about this? Event casting base to derived and derived to base yields the same pointer, and they both point same address. Seems to me, that the only difference is the type at compilation time. – noisy cat Oct 07 '16 at 13:34
  • That is one particular C++ implementation. The C++ standard does not require this layout; and even the same C++ implementation may choose to use a different layout for other classes. – Sam Varshavchik Oct 07 '16 at 13:58
  • @Sam took me 3 years and few days to understand your point. Thanks for patience. – noisy cat Oct 17 '19 at 00:37
1

No, this is unsafe.

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

  • the dynamic type of the object,
  • a cv-qualified version of the dynamic type of the object,
  • a type similar (as defined in 4.4) to the dynamic type of the object,
  • a type that is the signed or unsigned type corresponding to the dynamic type of the object,
  • a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
  • an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
  • a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
  • a char or unsigned char type.

Shamelessly stolen from Ben

None of those cases is true in the case of casting Base** to Derived**.

Community
  • 1
  • 1
Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122