1

私は以下のコードを試していました:

クラスのオブジェクトを指しているポインタopengascap()で車のクラスを呼び出していますが、ポイントされているオブジェクトの機能を提供しています。私の質問は、関数名がクラスに存在しないのに、なぜ出力が「fire」になるのかということです。car*nuclear**nuclear***nuclearsubmarine

  #include <iostream>

  using namespace std;

  class Vehicle 
  {
      public:
      virtual ~Vehicle() { }
      virtual void startEngine() = 0;
  };

  class Car : public Vehicle 
  {
  public:
      virtual void startEngine()
      {
          cout<<"start car";
      }
      virtual void openGasCap()
      {
      cout<<"open";
      }
  };

  class NuclearSubmarine : public Vehicle 
  {
      public:
      virtual void startEngine()
      {
          cout<<"start ship";
      }
      virtual void fireNuclearMissle()
      {
          cout<<"fire";
      }
  };

 int main()
 {
     Car   car;
     Car*  carPtr = &car;
     NuclearSubmarine  sub;
     NuclearSubmarine* subPtr = &sub;
     carPtr=(Car*)subPtr;
     // This last line would have caused carPtr to point to sub !
     carPtr->openGasCap();  // This might call fireNuclearMissle()!
     return 0;
 }

Ouput:火

4

1 に答える 1

4

NuclearSubmarinetypeへのポインターで type のオブジェクトを指していますCar。これらのタイプは無関係であるため、それはできません。

このエラーは未定義の動作を引き起こし、プログラムが予測できない方法で動作する原因となります。


このようなことが起こる理由に興味がある場合は、仮想関数が内部でどのように実装されているかを読んでください:仮想メソッド テーブル。これにより、物事が明確になります。


コメントへの返信:

それは正しい。Vehicleすべてのインスタンスには、次のような vtable を指す内部ポインターがあります。

0: Vehicle::~Vehicle
1: Vehicle::StartEngine                   // this is a null pointer

Carインスタンスは、次のような vtable を指す vptr を持っています。

0:Vehicle::~Vehicle                       // this one didn't get overridden
1:Car::startEngine
2:Car::openGasTrap

NuclearSubmarineの vtable は次のようになります。

0:Vehicle::~Vehicle                       // this one didn't get overridden
1:NuclearSubmarine::startEngine
2:NuclearEngine::fireNuclearMissile.

これがあれば、

Vehicle* v = new Car();
v->startEngine();

次のようなものにコンパイルされます(疑似コードを先に):

Vehicle* v = new Car();
// OK, v is a Vehicle and startEngine is a Vehicle's virtual function of index 1
// let's call it!
StartEngineFunc* f = v.VPTR[1]; // look up the vtable using the object's virtual pointer
CallMethod(v, f);

仮想ポインターは、オブジェクトの実際のランタイム型を使用して、正しい vtable への関数ルックアップを参照します。

これにより、派生クラスはその親クラスに対応する仮想テーブル (その最初の部分) を持つため、基本クラスへのポインターを介して派生クラスのメソッドを呼び出すことができます。

ただし、2 つのクラスが親子関係によって関連付けられていない場合、それらの仮想テーブルは異なる意味を持ちます。それがあなたの場合に起こることです。

(vtableメカニズムは実装の詳細ですが、コンパイラでは非常に一般的ですが、強制されることはなく、コンパイラは自由に異なる方法で実装できるため、プログラムでそれを当てにしないでください。)

于 2012-08-01T06:15:24.303 に答える