8

I'm new to C++ and have a question about member variable polymorphism. I have the following class definitions -

class Car
{
    public:
        Car();
        virtual int getNumberOfDoors() { return 4; }
};

class ThreeDoorCar : public Car
{
    public:
        ThreeDoorCar();
        int getNumberOfDoors() { return 3; }
};

class CarPrinter
{
    public:
        CarPrinter(const Car& car);
        void printNumberOfDoors();

    protected:
        Car car_;
};

and implementation

#include "Car.h"

Car::Car()
{}

ThreeDoorCar::ThreeDoorCar()
{}

CarPrinter::CarPrinter(const Car& car)
: car_(car)
{}

void CarPrinter::printNumberOfDoors()
{
    std::cout << car_.getNumberOfDoors() << std::endl;
}

The problem is when I run the following, the getNumberOfDoors of the parent class is called. I can get around this issue by making the member variable Car a pointer, but I prefer to pass in the input by reference instead of by pointer (which I understand to be preferred). Could you tell me what I'm doing wrong? Thanks!

ThreeDoorCar myThreeDoorCar;
std::cout << myThreeDoorCar.getNumberOfDoors() << std::endl;

CarPrinter carPrinter(myThreeDoorCar);
carPrinter.printNumberOfDoors();
CornSmith
  • 1,957
  • 1
  • 19
  • 35
haginile
  • 456
  • 5
  • 14
  • 4
    This is a textbook example of [slicing][1] [1]: http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c – zindorsky Jun 11 '13 at 03:51

3 Answers3

10

By making a copy of the object you sacrifice its polymorphic abilities. Whatever type of car you pass, the copy will be of type Car (the base class), because that is what it is declared as.

If you want to keep using polymorphism either use a pointer or a reference. Here is the version using a reference:

class CarPrinter
{
public:
    CarPrinter(const Car& car);
    void printNumberOfDoors();

protected:
    const Car &car_;     // <<= Using a reference here
};

As you can see, this way you can continue using a constructor that takes a reference as argument. (These references don't have to be const, although const makes sense as long as the purpose of the CarPrinter is just printing.)

One potentially undesirable side-effect of this is that you can't change what the reference refers to after constructing the CarPrinter object. If you need to print the information for a different object, you'll have to create a new CarPrinter object for that. These objects would then really just act as (probably short-lived) wrappers around references.

If you don't like this, you can still continue passing a reference to the constructor, but turn it into a pointer by taking its address in the constructor implementation and then storing that.

jogojapan
  • 68,383
  • 11
  • 101
  • 131
  • 1
    Thanks. This works very well. This makes me think that passing by pointer might be the way to go then. I based my judgment on http://stackoverflow.com/questions/1650792/c-function-parameters-use-a-reference-or-a-pointer-and-then-dereference, so should I abide by an additional rule - to pass pointer when the it's assigned to a member variable that's polymorphic? Thanks again! – haginile Jun 11 '13 at 03:27
  • 2
    Well... I suppose the main advantage of passing a pointer is that it forces the user to add the address operator, i.e. `CarPrinter(&mycar)`. Some may say this looks ugly, but it does serve the purpose of highlighting the fact that you pass something that the function you pass it to may use in special ways. In particular, it may store the address and therefore rely on the object to continue to live. Making this syntactically obvious can be useful. But other people might say you should pass a reference but change the name of the class `CarRefPrinter` or something) to make clear how it operates. – jogojapan Jun 11 '13 at 03:32
  • 1
    @haginile Another approach may be to use `std::reference_wrapper` as a model. That's the Standard library's version of a reference wrapper. It's not used for printing, but in every other regard it's similar to what you are doing (and it does preserve polymorphism, too). It gets its argument passed by reference. Further details here: http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper – jogojapan Jun 11 '13 at 03:37
  • 2
    Passing a reference, especially a const ref, is mostly used to *copy* the argument; a raw pointer (in new code) is a non-owning pointer and *shows* that the object needs to be kept alive for some time, making the programmer aware of potential risks. Good comment @jogojapan – dyp Jun 11 '13 at 03:39
  • 2
    "*By making a copy of the object you sacrifice its polymorphic abilities*" - this is the best formulation of this issue I've heard, +1 :) – SomeWittyUsername Jun 11 '13 at 05:02
4

When you do:

Car m_car;

It will not treat the m_car instance polymorphically, even if Car has subclasses and virtual functions. It will just use Car functions. This is called static binding - it determines which function to call at compile time based on the static type (Car) .

You need a reference or pointer for it to be handled polymorphically via dynamic dispatch by looking up the correct virtual function via the virtual function table of the instance's dynamic type (e.g. ThreeDoorCar or TwoDoorCar etc) at runtime. Polymorphic call behaviour is achieved through pointers or references in combination with virtual function declarations. This is more or less a direct result of syntactically using values vs pointers/refs (See @kfmfe04's comment below).

Car* pCar;
Car& rCar = x_car;

Virtual members called via a pointer or reference (e.g. pCar->getNumberOfDoors() or rCar.getNumberOfDoors()) does a vtable lookup at run time (dynamic dispatch). Because only at runtime does it know the dynamic type of the instance.

But m_car.getNumberOfDoors() is a virtual member that is called directly, and the compiler knows at compile time the direct (static) type and function address, statically binding the function address (Car::getNumberOfDoors) at compile time.

Community
  • 1
  • 1
Preet Kukreti
  • 8,417
  • 28
  • 36
  • 2
    +1 @OP - think about it this way: when C++ was designed, they needed a way to distinguish between the semantics of "literally give me a car (never one of its subclasses)" and "give me a Car-type: may actually be a subclass of Car". In order to give you the latter, they deemed that one should use of pointers and references. – kfmfe04 Jun 11 '13 at 04:17
1

The problem is in this line of the CarPrinter constructor:

: car_(car)

This calls the compiler generated default copy constructor for the Car class, which ends up creating an instance of Car, not ThreeDoorCar.

Unfortunately, you'd need to pass the pointer, or pass by reference, but store the pointer. For example:

class CarPrinter
{
public:
    CarPrinter(const Car& car)
       :car_(&car) {};
...
protected:
    const Car* car_;
};
Trenin
  • 2,041
  • 1
  • 14
  • 20
  • 1
    That's a horrible idiom that will result in pain and suffering. Conventional C++ usage says that you don't keep references around past one function call, and I for one really wouldn't expect the CarPrinter to keep a pointer to the Car I passed. Also, this will fail to compile because the argument is a reference to const, but you try to store it in a pointer to non-const. – Sebastian Redl Jun 11 '13 at 09:44