8

我是 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();
4

3 回答 3

10

通过制作对象的副本,您牺牲了它的多态能力。无论您通过什么类型的汽车,副本都将是类型Car(基类),因为这就是它被声明的内容。

如果您想继续使用多态性,请使用指针或引用。这是使用参考的版本:

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

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

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

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

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

于 2013-06-11T03:16:21.693 回答
4

当你这样做时:

Car m_car;

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

您需要一个引用或指针,以便在运行时通过实例的动态类型(例如或等)的虚函数表查找正确的虚函数,通过动态调度对其进行多态处理。多态调用行为是通过指针或引用结合虚函数声明来实现的。这或多或少是在语法上使用值与指针/引用的直接结果(请参阅下面的 @ kfmfe04评论)。ThreeDoorCarTwoDoorCar

Car* pCar;
Car& rCar = x_car;

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

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

于 2013-06-11T03:21:06.367 回答
1

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

: car_(car)

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

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

class CarPrinter
{
public:
    CarPrinter(const Car& car)
       :car_(&car) {};
...
protected:
    const Car* car_;
};
于 2013-06-11T03:17:52.987 回答