views:

167

answers:

8

Suppose I have a class Dog that inherits from a class Animal. What is the difference between these two lines of code?

    Animal *a = new Dog();
    Dog *d = new Dog();

In one, the pointer is for the base class, and in the other, the pointer is for the derived class. But when would this distinction become important? For polymorphism, either one would work exactly the same, right?

+7  A: 

For all purposes of type-checking, the compiler treats a as if it could point to any Animal, even though you know it points to a Dog:

  • You can't pass a to a function expecting a Dog*.
  • You can't do a->fetchStick(), where fetchStick is a member function of Dog but not Animal.
  • Dog *d2 = dynamic_cast<Dog*>(d) is probably just a pointer copy on your compiler. Dog *d3 = dynamic_cast<Dog*>(a) probably isn't (I'm speculating here, I'm not going to bother checking on any compiler. The point is: the compiler likely makes different assumptions about a and d when transforming code).
  • etc.

You can call virtual functions (that is, the defined polymorphic interface) of Animal equally through either of them, with the same effect. Assuming Dog hasn't hidden them, anyway (good point, JaredPar).

For non-virtual functions which are defined in Animal, and also defined (overloaded) in Dog, calling that function via a is different from calling it via d.

Steve Jessop
+1  A: 

No, they aren't the same.

The Dog pointer is not as polymorphic as Animal. All it can point to at runtime is a Dog or a subclass of Dog. If there are no subclasses of Dog, then the Dog runtime type and compile time types are the same.

The Animal pointer can refer to any subclass of Animal: Dog, Cat, Wildebeast, etc.

duffymo
A: 

It makes no real difference at run time, as the two instances are the same. The only difference is at compile time, where you could call for example d->bark() but not a->bark(), even if a actually contains a dog. The compiler considers the variable to be an animal and only that.

Shtong
It matters if you go to call a non-virtual function of the object.
tloach
*"It makes no real difference at run time,...The only difference is at run time**
Adam Robinson
Yeah... I meant compile time sorry :]
Shtong
+4  A: 

The answer to this question is a giant: It depends

There are numerous ways in which the type of the pointer could become important. C++ is a very complex language and one of the ways it shows up is with inheritance.

Lets take a short example to demonstrate one of the many ways in which this could matter.

class Animal {
  virtual void MakeSound(const char* pNoise) { ... }
  virtual void MakeSound() { ... }
};

class Dog : public Animal {
  virtual void MakeSound() {... }
};

void Usage() {
  Animal* a = new Dog();
  Dog* d = new Dog();
  a->MakeSound("bark");
  d->MakeSound("bark"); // Does not compile
}

The reason why is a quirk of the way C++ does name lookup. In Short: When looking for a method to call C++ will walk the type hierarchy looking for the first type which has a method of the matching name. It will then look for a correct overload from the methods with that name declared on that type. Since Dog only declares a MakeSound method with no parameters, no overload matches and it fails to compile.

JaredPar
This can be overcome by sticking a "using Animal::MakeSound(const char*)" in the Dog class definition, I believe.
dash-tom-bang
@dash-tom-bang it definitely can but it's just an example of where the behavior can differ
JaredPar
No arguments with that. ;)
dash-tom-bang
A: 

The difference is important when you try to call Dog's methods that are not Animal's method. In the first case (pointer to Animal) you have to cast the pointer to Dog first. Another difference is if you happen to overload non-virtual method. Then either Animal::non_virtual_method() (pointer to Animal) or Dog::non_virtual_method(pointer to Dog) will be called.

a1ex07
+2  A: 

The first line allow you to call only members of the Animal class on a :

Animal *a = new Dog();
a->eat(); // assuming all Animal can eat(), here we will call Dog::eat() implementation.
a->bark(); // COMPILATION ERROR : bark() is not a member of Animal! Even if it's available in Dog, here we manipulate an Animal.

Although (as pointed by others), in this cas as a is still an Animal, you can't provide a as a parameter of a function asking for a more specific child class that is Dog :

void toy( Dog* dog );

toy( a ); // COMPILATION ERROR : we want a Dog!

The second line allow you to use specific functions of the child class :

Dog *a = new Dog();
a->bark(); // works, but only because we're manipulating a Dog

So use the base class as the "generic" interface of your class hierarchy (allowing you to make all your Animals to eat() whithout bothering about how).

Klaim
+1  A: 

The distinction is important when you call a virtual function using the pointer. Let's say Animal and Dog both have functions called do_stuff().

  1. If Animal::do_stuff() is declared virtual, calling do_stuff() on an Animal pointer will call Dog::do_stuff().

  2. If Animal::do_stuff() is not declared virtual, calling do_stuff() on an Animal pointer will call Animal::do_stuff().

Here's a full working program to demonstrate:

#include <iostream>

class Animal {
public:
        void do_stuff() { std::cout << "Animal::do_stuff\n"; }
        virtual void virt_stuff() { std::cout << "Animal::virt_stuff\n"; }
};

class Dog : public Animal {
public:
        void do_stuff() { std::cout << "Dog::do_stuff\n"; }
        void virt_stuff() { std::cout << "Dog::virt_stuff\n"; }
};

int main(int argc, char *argv[])
{
        Animal *a = new Dog();
        Dog *b = new Dog();

        a->do_stuff();
        b->do_stuff();
        a->virt_stuff();
        b->virt_stuff();
}

Output:

Animal::do_stuff
Dog::do_stuff
Dog::virt_stuff
Dog::virt_stuff

This is just one example. The other answers list other important differences.

indiv
A: 

You must always remember there are 2 parts in every class, the data and the interface.

Your code truly created 2 Dog objects on the heap. Which means the data is of Dog. This object is of size the sum of all data members Dog + Animal + the vtable pointer.

The ponters a and d (lvalues) differ as from a interface point of view. Which determines how you can treat them code wise. So even though Animal* a is really a Dog, you could not access a->Bark() even if Dog::Bark() existed. d->Bark() would have worked fine.

Adding the vtable back into the picture, assuming the interface of Animal had Animal::Move a generic Move() and that Dog really overwriten with a Dog::Move() { like a dog }.

Even if you had Animal a* and performed a->Move() thanks to the vtable you would actually Move() { like a dog }. This happens because Animal::Move() was a (virtual) function pointer re-pointed to Dog's::Move() while constructing Dog().

ruralcoder