成员变量多态性&参考的论点

时间:2013-06-11 03:11:33

标签: c++

我是C ++的新手,对成员变量多态性有疑问。我有以下类定义 -

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_;
};

和实施

#include "Car.h"

Car::Car()
{}

ThreeDoorCar::ThreeDoorCar()
{}

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

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

问题是当我运行以下命令时,会调用父类的getNumberOfDoors。我可以通过使成员变量Car成为指针来解决这个问题,但我更喜欢通过引用而不是指针(我理解为首选)传入输入。你能告诉我我做错了什么吗?谢谢!

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

CarPrinter carPrinter(myThreeDoorCar);
carPrinter.printNumberOfDoors();

3 个答案:

答案 0 :(得分:9)

通过制作对象的副本,你牺牲了它的多态性能力。无论你传递什么类型的汽车,副本都是Car类型(基类),因为它是声明的。

如果要继续使用多态,请使用指针或引用。以下是使用引用的版本:

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

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

如您所见,通过这种方式,您可以继续使用将引用作为参数的构造函数。 (这些引用不必是const,但只要const的目的只是打印,CarPrinter就有意义。)

一个潜在的不良副作用是,在构造CarPrinter对象后,您无法更改引用引用的内容。如果需要打印其他对象的信息,则必须为此创建新的CarPrinter对象。这些对象实际上只是作为参考文件的(可能是短暂的)包装器。

如果您不喜欢这样,您仍然可以继续传递对构造函数的引用,但是通过在构造函数实现中获取其地址然后存储它来将其转换为指针。

答案 1 :(得分:3)

当你这样做时:

Car m_car;

即使m_car具有子类和虚函数,它也不会以多态方式处理Car实例。它只会使用Car个函数。这称为静态绑定 - 它根据静态类型Car)确定在编译时调用哪个函数。

您需要通过 dynamic dispatch 以多态方式处理它的引用或指针,方法是通过实例的虚函数表查找正确的虚函数运行时动态类型(例如ThreeDoorCarTwoDoorCar等)。通过指针或引用结合虚函数声明来实现多态调用行为。这或多或少是语法上使用值vs pointers / refs的直接结果(参见下面的@ kfmfe04注释)。

Car* pCar;
Car& rCar = x_car;

通过指针或引用(例如pCar->getNumberOfDoors()rCar.getNumberOfDoors())调用的虚拟成员在运行时执行vtable查找(动态调度)。因为只有在运行时它才知道实例的动态类型

m_car.getNumberOfDoors()是一个直接调用的虚拟成员,编译器在编译时知道直接(静态)类型和函数地址,静态绑定编译时的函数地址(Car::getNumberOfDoors)。

答案 2 :(得分:1)

问题在于CarPrinter构造函数的这一行:

: car_(car)

这将为Car类调用编译器生成的默认复制构造函数,最终创建Car的实例,而不是ThreeDoorCar

不幸的是,您需要传递指针或通过引用传递,但存储指针。例如:

class CarPrinter
{
public:
    CarPrinter(const Car& car)
       :car_(&car) {};
...
protected:
    const Car* car_;
};