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

これの潜在的に望ましくない副作用の 1 つは、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()ポインターまたは参照 (または など)を介して呼び出される仮想メンバーrCar.getNumberOfDoors()は、実行時に vtable ルックアップを行います (動的ディスパッチ)。インスタンスの動的な型を知るのは実行時だけだからです。

ただしm_car.getNumberOfDoors()、直接呼び出される仮想メンバーであり、コンパイラはコンパイル時に直接 ( static ) 型と関数アドレスを認識し、コンパイル時に関数アドレス ( ) を静的にバインドCar::getNumberOfDoorsします。

于 2013-06-11T03:21:06.367 に答える
1

問題は、CarPrinter コンストラクターの次の行にあります。

: car_(car)

これにより、コンパイラが生成したクラスの既定のコピー コンストラクターが呼び出され、最終的にではなくCarのインスタンスが作成されます。CarThreeDoorCar

残念ながら、ポインターを渡すか、参照渡しする必要がありますが、ポインターを保存します。例えば:

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