views:

842

answers:

5

When I allocate a single object, this code works fine. When I try to add array syntax, it segfaults. Why is this? My goal here is to hide from the outside world the fact that class c is using b objects internally. I have posted the program to codepad for you to play with.

#include <iostream>

using namespace std;

// file 1

class a
{
    public:
     virtual void m() { }
     virtual ~a() { }
};

// file 2

class b : public a
{
    int x;

    public:
     void m() { cout << "b!\n"; }
};

// file 3

class c : public a
{
    a *s;

    public:
     // PROBLEMATIC SECTION
     c() { s = new b[10]; } // s = new b;
     void m() { for(int i = 0; i < 10; i++) s[i].m(); } // s->m();
     ~c() { delete[] s; } // delete s;
     // END PROBLEMATIC SECTION
};

// file 4

int main(void)
{
    c o;

    o.m();

    return 0;
}
+9  A: 

Newing an array of 10 b's and assigning its address to an a* is asking for trouble. You can't treat arrays polymorphically.

Daniel Daranas
I cannot disagree, so deleted my comment.
Ari
Thank you Ari for your understanding. There was no need to delete your comment, really. Anyway, now I deleted my reply :)
Daniel Daranas
+4  A: 

You have an array of type "b" not of type "a" and you are assigning it to a pointer of type a. Polymorphism doesn't transfer to dynamic arrays.

 a* s

to a

 b* s

and you will see this start working.

Only not-yet-bound pointers can be treated polymorphically. Think about it

 a* s = new B(); // works
 //a* is a holder for an address

 a* s = new B[10]
 //a* is a holder for an address
 //at that address are a contiguos block of 10 B objects like so
 // [B0][B2]...[B10] (memory layout)

when you iterate over the array using s, think about what is used

 s[i]
 //s[i] uses the ith B object from memory. Its of type B. It has no polymorphism. 
 // Thats why you use the . notation to call m() not the -> notation

before you converted to an array you just had

 a* s = new B();
 s->m();

s here is just an address, its not a static object like s[i]. Just the address s can still be dynamically bound. What is at s? Who knows? Something at an address s.

See Ari's great answer below for more information about why this also doesn't make sense in terms of how C style arrays are layed out.

Doug T.
s->m(); can be rewritten s[0].m(); and it will work
Martin
@Ari, thanks, you're correct. I was thinking that as I was typing it. I more want him to think about what is "used" as opposed to something being "loaded".
Doug T.
+6  A: 

One problem is that the expression s[i] uses pointer arithmetic to compute the address of the desired object. Since s is defined as pointer to a, the result is correct for an array of as and incorrect for an array of bs. The dynamic binding provided by inheritance only works for methods, nothing else (e.g., no virtual data members, no virtual sizeof). Thus when calling the method s[i].m() the this pointer gets set to what would be the ith a object in the array. But since in actuality the array is one of bs, it ends up (sometimes) pointing to somewhere in the middle of an object and you get a segfault (probably when the program tries to access the object's vtable). You might be able to rectify the problem by virtualizing and overloading operator[](). (I Didn't think it through to see if it will actually work, though.)

Another problem is the delete in the destructor, for similar reasons. You might be able to virtualize and overload it too. (Again, just a random idea that popped into my head. Might not work.)

Of course, casting (as suggested by others) will work too.

Ari
+1  A: 

Each instance of B contains Both X data member and the "vptr" (pointer to the virtual table).

Each instance of A contain only the "vptr"

Thus , sizeof(a) != sizeof(b).

Now when you do this thing : "S = new b[10]" you lay on the memory 10 instances of b in a raw , S (which has the type of a*) is getting the beginning that raw of data.

in C::m() method , you tell the compiler to iterate over an array of "a" (because s has the type of a*) , BUT , s is actualy pointing to an array of "b". So when you call s[i] what the compiler actualy do is "s + i * sizeof(a)" , the compiler jumps in units of "a" instead of units of "b" and since a and b doesn't have the same size , you get a lot of mambojumbo.

+1 for good explanation and use of mambojumbo.
Martin
+1  A: 

I have figured out a workaround based on your answers. It allows me to hide the implementation specifics using a layer of indirection. It also allows me to mix and match objects in my array. Thanks!

#include <iostream>

using namespace std;

// file 1

class a
{
    public:
        virtual void m() { }
        virtual ~a() { }
};

// file 2

class b : public a
{
    int x;

    public:
        void m() { cout << "b!\n"; }
};

// file 3

class c : public a
{
    a **s;

    public:
        // PROBLEMATIC SECTION
        c() { s = new a* [10]; for(int i = 0; i < 10; i++) s[i] = new b(); }
        void m() { for(int i = 0; i < 10; i++) s[i]->m(); }
        ~c() { for(int i = 0; i < 10; i++) delete s[i]; delete[] s; }
        // END PROBLEMATIC SECTION
};

// file 4

int main(void)
{
    c o;

    o.m();

    return 0;
}
Martin
Use std::vector<boost::smart_ptr<a> > instead!!
jmucchiello
If the OP insists on using arrays though, this is indeed the correct way to get an array of polymorphic things.
newacct