views:

371

answers:

6

Hi all,

I have a class A:

class A
{
    public:
        virtual double getValue() = 0;
}

And a class B:

class B : public A
{
    public:
        virtual double getValue() { return 0.0; }
}

And then in main() I do:

A * var;
var = new B[100];
std::cout << var[0].getValue(); //This works fine
std::cout << var[1].getValue(); //This, or any other index besides 0, causes the program to quit

If instead I do:

B * var;
var = new B[100];
std::cout << var[0].getValue(); //This works fine
std::cout << var[1].getValue(); //Everything else works fine too

Everything compiles fine, but it seems as though there is something wrong with my polymorphism perhaps? I'm puzzled.

A: 

You didn't allocate the objects in the array:

for (int i=0;i<100;i++)
  var[i] = new B;

(although I may be mixing up C++ and C#)

Philippe Leybaert
Don't think that's correct. It's an array of B objects, not an array of pointers to B objects.
Glen
`var` is a pointer variable and not a pointer to a pointer so `var[i] = new B` isn't valid (unless there's an unusually defined assignment operator taking a pointer).
Charles Bailey
That's incorrect. `char * c = new char[100]; for(...) c[i] = foo;` works
Paul Nathan
No, you got it right. Most OO languages (that I've used) work the same way for creating arrays of objects.
That would require var to be an array of pointers. B ** var;
Alan
@Paul Nathan: ... only if `foo` is a `char` and not a `char*` . (To complete the analogy with the posted code.)
Charles Bailey
+7  A: 

You can't treat arrays polymorphically, so while new B[100] creates an array of B objects and returns a pointer to the array - or equivalently the first element of the array - and while it is valid to assign this pointer to a pointer to a base class, it is not valid to treat this as a pointer into an array of A objects.

The principal reason that you can't is that (typically) derived objects are a different size to their base classes, so attempting to access the array as an array of base class objects will not use the correct offset to get a pointer to the next base class subobject of the next member of the derived class array.

Charles Bailey
@Charles I'm assuming if he changes his array to A ** var then this restriction won't apply (and properly initialises it by new'ing all 100 items)
Glen
Ahh, I see. So I'd want to create an array of 100 pointers to A instead, right?
@Glen: Not quite sure what your driving at. You'd have an array of pointers and not an array of objects. Because pointers don't derive from other pointers the problem doesn't arise.
Charles Bailey
@Charles. If he had an array of pointers to A, then he could do var[i] = new B(); Then call var[i]->getValue() and B::getValue() would be called correctly
Glen
@Stewart: What are you ultimately trying to achieve? If you have an array of pointers to dynamically created objects then you have a lot of memory management issues. What's wrong with keeping an array of B objects and just retaining the correct type?
Charles Bailey
@Glen: Well, yes, it works but now he has to manually allocate 100 objects and, at some point, deallocate them. Personally, I'd go for the array of objects (or more likely I'd use a `std::vector<B>`).
Charles Bailey
Ultimately I'll have a rectangular matrix of objects deriving from A, which could be of 10 or so different sub-classes, so I define an update() method in A, and each subclass updates itself in a different way.
@Charles, sure, but if he wants to put B, C, D and E objects into the array then he's gonna have to use pointers. (and I see by his latest comment that this is what he wants)
Glen
@Glen: With the use of `new B[100]` this seemed unlikely, but it looks like I was mislead.
Charles Bailey
I apologise, I didn't mean to mislead; the matrix will be predominantly of one type, with smaller numbers of various other sub-classes. Is there any advantage to your suggestion with vectors over just an array of pointers to A, that I can then delete and replace with a new B?
If you have boost available, consider using a `std::vector< boost::ptr_vector< A > >`. If you avoid having to manually `delete` your dynamically created objects then you won't have any leaks.
Charles Bailey
@Charles, sorry for the confusion. + I really wish I had boost here. Stuff like that boost::ptr_vector looks really useful!
Glen
Okay, thanks. But just to confirm, what I most recently said is the correct basic way to handle this?
If you don't have boost, but have tr1 you could use a `vector< tr1::shared_ptr< A > >`. If you don't have `tr1` I really would consider writing (e.g. by copying a tried and tested!) smart pointer or smart pointer container. But, otherwise, yes, you can have a 2d array something like `A *p[N][M];` and allocate and deallocate each member seperately: `p[x][y] = new B;` `delete p[x][y]`. Remember to make `A`'s destructor virtual.
Charles Bailey
A: 

Polymorphism works through a pointer AFAIK

var[1]->getValue()

Edit:

B* var = new B[100];

A* getOne(int idx) { return &var[idx]; }

getOne(1)->getValue()
sylvanaar
even with the edit this won't work sylvanaar. The thing in the array is an object, not a pointer. Using the -> operator won't even compile
Glen
can you even make an array of A since its abstract?
sylvanaar
ok - so i made a couple more edits.
sylvanaar
@sylvanaar, no you can't. But the OP didn't try to do that.
Glen
sylvanaar, now you've got a slicing problem. If you want to use polymorphism + arrays then you need to store pointers in the array NOT objects. Stop trying to flog a dead horse.
Glen
i understand what is in the array - and you don't have to store pointers in the array.
sylvanaar
If you want to use the contents of the array in a polymorphic fashion then you do. Read Charles Bailey's answer to see why.
Glen
THe OP did this: A* var; then var[1]. Which would require an array of A.
sylvanaar
no, he did this, A*var; var=new B[100]; This is subtly different. At no point did this code try to create an object of type A.
Glen
A: 

Technically both should work. The thing about polymorphism is that the proper function's implementation is selected at runtime. Since you assign the two var's to new B[100] they should both have a B's version of getValue(). What compiler/version are you using?

mcjabberz
Not true, you can't just treat arrays polymorphically. You can obtain pointers to objects in the array and use these as single object pointers in a polymorphic ways, but anything that relies on jumping the correct offset between array memebers has to use a pointer of the same static type as the original array member type.
Charles Bailey
+3  A: 

There is no problem with the polymrphism but with the way you are dealing with memory. The [] operator will advance you through the array by the sizeof(A) bytes in the first case and the sizeof(B) bytes in the second case. Because the objects are of type B the A* is not pointing to the correct location in memory.

Here is another way of looking at it

char * var;
var = (char*) new B[100];
std::cout << ((A*)var[0]).getValue(); //This works fine
std::cout << ((A*)var[1]).getValue(); //This will fail
std::cout << ((A*)var[sizeof(B)]).getValue(); // should work
ridale
+1  A: 

Read The C++ FAQ 21.2-21.4.

sbi