I am new to C++ and have gone through many of the online tutorials and books that are available. But one concept eludes me, and I am hoping someone can help clarify. I have searched forums for discussions on this and the answers are still not helpful. So I am trying here.
If I have a Base class and a Derived class, C++ allows me to do this:
Base * b;
b = new Derived();
but not this:
Derived * d;
d = new Base();
Why? Now I understand the intuitive arguments, such as: say Base is a Car, and Derived is a Nissan car, then all Nissan objects are Cars but all Cars are not Nissans. But in a nuts and bolts sense, I do not understand why C++ will scream. I can understand if you tell me that it is just a rule and that's it, but I understand there is actually some reason why C++ does not allow it. What is the reason?
Let me explain my confusion further below. Sorry for the long post, but hopefully it will avoid a chain of clarifications.
When you say
int * i = new int;
you are allocating space for an int and assigning a pointer to that space. Now suppose Base is simply
class Base {
int i;
public:
int get_i() {return i;}
}
and Derived is
class Derived: public Base {
int j;
}
Clearly, Derived has more instructions and data than Base, so it would seem to me that if you said
Derived * d;
you are allocating space for a Derived object, which should be more than the space for a Base * b pointer. So it seems to me that assigning this d pointer to a Base class would leave us with a little extra room in the storage that d is pointing to, and so is not a problem. On the other hand, it looks like if you created
Base * b;
it only creates enough storage for a base pointer, and would so have difficulty accommodating a Derived object if we do say
b = new Derived();
So why is this allowed and not the other way around?
Thanks very much for any clarification! I have looked around a lot and don't seem to find any clear answers.
class Shape
{
public:
virtualvoid draw() = 0;
//...
};
public Square
{
int length;
public:
virtualvoid draw();
//...
};
public Rectangle
{
int length, width;
public:
virtualvoid draw();
//...
};
public Circle
{
double radius;
public:
virtualvoid draw();
//...
};
You could draw a collection of them as:
1 2 3 4 5 6 7
typedef std::vector<Shape*> shapes_type;
void Draw(shapes_type &shapes)
{
for (shapes_type::iterator p = shapes.begin(); p != shapes.end(); ++p)
(*p)->draw();
}
That's what polymorphism is about, implementing protocol and using them in abstract ways.
Now, for your nuts and bolts.
Consider this code:
1 2
Circle* c = ShapeFactory::CreateCircle(infile);
Shape* s = c; // is this ok?
Yes, it is. You already know that.
How about this.
1 2 3 4
void func(Shape* s)
{
Rectangle* r = s; // is this ok?
}
Well, what if s was actually a Circle? What would the layout be? You be saying that some thing that has a double can treated as something that has two ints, of course this is wrong. You're reinterpret the structure incorrectly. If you want to override the compiler's type system to do this, you can use the reinterpret_cast, but you'd be wrong to do so.
1 2 3 4
void func(Shape* s)
{
Rectangle* r = reinterpret_cast<Rectangle*>(s); // this is wrong
}
C++ can maintain runtime type information, a feature generally called RTTI. This allows the language to track the type and only do the conversion if it was a Rectangle to begin with. You use dynamic_cast to do this. It returns the null pointer if it's not the right type.
1 2 3 4 5 6
void func(Shape* s)
{
Rectangle* r = dyamic_cast<Rectangle*>(s); // this ok
if (r == 0)
return;
}
kbw's answer is good. I just want to touch on a question he didn't seem to get to:
Clearly, Derived has more instructions and data than Base, so it would seem to me that if you said
Derived * d;
you are allocating space for a Derived object, which should be more than the space for a Base * b pointer. So it seems to me that assigning this d pointer to a Base class would leave us with a little extra room in the storage that d is pointing to, and so is not a problem. On the other hand, it looks like if you created
Base * b;
it only creates enough storage for a base pointer, and would so have difficulty accommodating a Derived object if we do say
b = new Derived();
So why is this allowed and not the other way around?
I think you're misinterpretting allocation. "Derived* d" doesn't allocate any space for the Derived object, it just makes a pointer to it. If you have "Derived* d" and "Base* B".. both d and b are the same size (the size of one pointer). What actually allocates the object is the new call.
So let's say that a 'Base' object is 4 bytes and a 'Derived' object is 8 bytes. If you do the following:
Base* b = new Derived;
The 'new Derived' allocates 8 bytes of space, but because we're treating it like a Base pointer, we're only accessing 4 bytes of it. On the other hand:
Derived* d = new Base;
This only allocates 4 bytes for the base, but the 'Derived' pointer could acces 8 bytes (more than we allocated!) would would be really bad.
This is why that's not allowed (among other reasons).
Thanks to Disch and kbw for both your informative posts. These definitely help. One followup I have to Disch's response is this:
You say that "Derived * d" does not allocate any space for the Derived object, it just makes a pointer to it. You also say that "Derived * d" and "Base * b" both allocate only a size of one pointer. If this is so, why bother to say Base * b and Derived *d ? Why not simply say
pointer * p; // or maybe, even simpler,
*p; // so you know this is a pointer variable
p = new Derived;
or
p = new Base;
so then what p is a pointer for is only determined after this line. This way, p only contains the address of the location ( 1 hexadecimal) at which a new Derived or Base class will sit in memory. Why is it not implemented this way? It seems to me it would make things simpler. Am I mistaken? Is there really a well-thought-out reason why you have to specify what kind of pointer p is?
Thanks again for the prompt and helpful responses!
void* p; declares a pointer to something (anything). But to use some memory, you have to interpret it. That interpretation is what we call type, and that imposes rules on how to encode/decode those bits and what can be inferred.
When a variable Base* p is declared, you don't just get the space for a pointer, you get a whole buch of implied rules on how that pointer is used, and how to interpret the stuff that is pointed to by that pointer.