0

I am trying to understand Polymorphism behavior in C++ code. I got surprised by seeing the output of the the below program.

The output of the below code I was expecting some compilation error/crash at following programming statement. But output of this program quite surprised me.

p = (Class1*) &object3;
p->f();
p->g();

I couldn't understand why. I am using Visual studio 2008.

Code Snippet.

#include "stdafx.h"
#include <iostream>

using namespace std;

class Class1
{
   public:
      virtual void f()
      {
    cout << "Function f() in Class1\n";
      }

     void g()
     {
    cout << "Function g() in Class1\n";
     }
 };


 class Class2
 {
    public:
   virtual void f()
   {
    cout << "Function f() in Class2\n";
   }

   void g()
   {
    cout << "Function g() in Class2\n";
   }
 };


class Class3
{
    public:

    virtual void h()
    {
    cout << "Function h() in Class3\n";
    }
  };


  int _tmain(int argc, _TCHAR* argv[])
  {
   Class1 object1, *p;
   Class2 object2;
   Class3 object3;

  p = &object1;

  p->f();
  p->g();

  p = (Class1*) &object2;

  p->f();
  p->g();

  p = (Class1*) &object3;

  p->f();
  p->g();

  //p->h();      Compilation error

  return 0;

   }

O/P:

Function f() in Class1

Function g() in Class1

Function f() in Class2

Function g() in Class1

Function h() in Class3

Function g() in Class1

4

3 に答える 3

4

この場合は に相当する邪悪な C スタイルのキャストを使用して、reinterpret_castあるクラスへのポインターを取得し、それが無関係なクラスを指しているふりをしています。

コンパイルエラー/クラッシュが予想されていました

コンパイラが型変換を意図的にチェックしないようにしたため、コンパイル エラーは発生しません。それが の目的でありreinterpret_cast、それを回避する理由です (C スタイルのキャストはさらにそうです)。ただし、本当に必要な場合を除きます。クラッシュ、またはその他の未定義のランタイム動作が発生する可能性があります。

あなたの場合、ポインターの型が完全に間違っていても、クラスのレイアウトは仮想関数呼び出しが成功するのに十分似ているようです。クラス定義はすべて非常に似ているため、これは驚くべきことではありません。しかし、これは動作が保証されているわけではなく、原則として、多くの壊滅的な方法で失敗する可能性があります。

ポリモーフィックな動作が必要な場合は、Class2Class3を から継承する必要がありClass1ます。ポインターの変換はキャストなしで有効になり、多態的な動作が明確に定義されます。f()つまり、オブジェクトの型に応じて仮想的にディスパッチさg()れ、ポインターの型に応じて非仮想的にディスパッチされます。

于 2013-02-18T17:22:46.473 に答える
3

あなたのコードにはUndefined Behaviorがあります。

プログラムに未定義の動作がある場合、すべてが発生する可能性があります。クラッシュする可能性がありますが、これは必須ではありません。パラグラフ1.3.24から:

[...] 許容される未定義の動作は、状況を完全に無視して予測不可能な結果を​​もたらすことから、翻訳またはプログラムの実行中に、環境に特有の文書化された方法で動作すること (診断メッセージの発行の有無にかかわらず)、翻訳の終了、または実行 (診断メッセージの発行を伴う)。[...]


未定義の動作があるのはなぜですか?

型のオブジェクトへのポインタから、型のオブジェクトへのポインタへの残忍な C スタイルのキャスト ( に頼るreinterpret_cast<>) を実行しています。これは許可されていますが、動作は未定義です。Class3Class1

C++ 11標準のパラグラフ5.2.10/7からreinterpret_cast<>

オブジェクト ポインターは、異なる型のオブジェクト ポインターに明示的に変換できます。70 [..]

これにより、明示的なキャストが有効になります。ただし(同じ段落):

[...] 「T1 へのポインター」型の prvalue v が「cv T2 へのポインター」型に変換される場合、T1 と T2 の両方が標準レイアウト型(3.9 ) であり、T2 のアラインメント要件は T1 のアラインメント要件よりも厳密ではないか、いずれかの型が void である場合。[...]

あなたのクラスClass1Class3は、標準レイアウト タイプではありません。パラグラフ 9/7 によると、実際には:

標準レイアウト クラスは、次のようなクラスです。

— 非標準レイアウト クラス (またはそのような型の配列) 型または参照型の非静的データ メンバーがない。

仮想関数(10.3) も仮想基本クラス (10.1) もありません。

[...]

したがって、5.2.10/7 の 2 番目の部分が適用されます。

[...] 「T1 へのポインター」型の prvalue を「T2 へのポインター」型 (ここで、T1 と T2 はオブジェクト型であり、T2 のアラインメント要件は T1 のアラインメント要件よりも厳密ではありません) に変換し、元の値に戻します。元の型は元のポインター値を生成します。他のそのようなポインター変換の結果は規定されていません

そのポインターを変換した結果は指定されていないため、そのポインターに対して関数を呼び出そうとすると、未定義の動作が発生します。

于 2013-02-18T17:19:40.237 に答える
1

あなたのコードはポリモーフィズムのサンプルではありません。オブジェクトポインタを互いにキャストしているだけです。

ポリモーフィズムは継承に関連しているため、クラスを相互に拡張 (継承) するのを忘れていました。

たとえば、これを試してください:

class Class1
{
   public:
      virtual void f()
      {
    cout << "Function f() in Class1\n";
      }

     void g()
     {
    cout << "Function g() in Class1\n";
     }
 };


 class Class2 : public Class1
 {
    public:
   virtual void f()
   {
    cout << "Function f() in Class2\n";
   }

   void g()
   {
    cout << "Function g() in Class2\n";
   }
 };


class Class3 : public Class2
{
    public:

    virtual void h()
    {
    cout << "Function h() in Class3\n";
    }
  };
于 2013-02-18T17:18:28.447 に答える