7

次の単純な状況を考えてみましょう:

A.h

class A {
public:
    virtual void a() = 0;
};

B.h

#include <iostream>

class B {
public:
    virtual void b() {std::cout << "b()." << std::endl;};
};

C.h

#include "A.h"
#include "B.h"

class C : public B, public A {
public:
    void a() {std::cout << "a() in C." << std::endl;};
};

int main() {
    B* b = new C();
    ((A*) b)->a();    // Output: b().

    A* a = new C();
    a->a();           // Output:: a() in C.

   return 0;
}

言い換えれば:
- A は純粋な仮想クラスです。
- B は​​、スーパー クラスがなく、非純粋仮想関数が 1 つあるクラスです。
- C は A および B のサブクラスであり、A の純粋仮想関数をオーバーライドします。

私を驚かせたのは、最初の出力です。

((A*) b)->a();    // Output: b().

コード内で a() を呼び出していますが、 b() が呼び出されます。私の推測では、変数 b がクラス A のサブクラスではないクラス B へのポインターであるという事実に関連していると思いますが、それでもランタイム型は C インスタンスへのポインターです。

Javaの観点から、これを説明する正確なC++ルールは何ですか?奇妙な動作?

4

8 に答える 8

24

C スタイルのキャストを使用して無条件にキャストbしています。コンパイラは、これを行うことを妨げません。あなたはそれがであると言ったので、それはです。したがって、それが指すメモリを のインスタンスのように扱います。はの vtable にリストされている最初のメソッドであり、 は の vtable にリストされている最初のメソッドであるため、実際には であるオブジェクトを呼び出すと、 が得られます。A*A*A*Aa()Ab()Ba()Bb()

オブジェクトのレイアウトが似ていることは幸運です。これが当てはまるとは限りません。

まず、C スタイルのキャストを使用しないでください。より安全なC++ キャスト演算子を使用する必要があります (ただし、自分自身を撃つことはできますが、ドキュメントを注意深く読んでください)。

第 2 に、 を使用しない限り、この種の動作に依存するべきではありませんdynamic_cast<>

于 2010-01-20T21:59:14.777 に答える
11

多重継承ツリー全体でキャストする場合は、C スタイルのキャストを使用しないでください。dynamic_cast代わりに使用すると、期待される結果が得られます。

B* b = new C();
dynamic_cast<A*>(b)->a();
于 2010-01-20T22:00:13.280 に答える
5

B* から始めて、A* にキャストしています。2つは無関係であるため、未定義の動作の領域を掘り下げています.

于 2010-01-20T22:00:33.857 に答える
2

((A*) b)明示的な c スタイルのキャストであり、指定された型に関係なく許可されます。ただし、このポインターを逆参照しようとすると、実行時エラーまたは予測不能な動作になります。これは後者の例です。あなたが観察した出力は、決して安全でも保証されたものでもありません。

于 2010-01-20T22:00:20.270 に答える
2

ABは継承によって相互に関連していません。つまり、へのポインターは、アップキャストまたはダウンキャストによってBへのポインターに変換することはできません。A

ABは の 2 つの異なるベースであるためC、ここでやろうとしていることはクロスキャストと呼ばれます。クロスキャストを実行できる C++ 言語での唯一のキャストはdynamic_cast. これは、本当に必要な場合に備えて、この場合に使用する必要があるものです (そうですか?)

B* b = new C(); 
A* a = dynamic_cast<A*>(b);
assert(a != NULL);
a->a();    
于 2010-01-20T22:19:26.447 に答える
1

次の行は reinterpret_cast です。これは同じメモリを指していますが、別の種類のオブジェクトであると「偽装」しています。

((A*) b)->a();

本当に必要なのは、オブジェクト b の種類を確認し、メモリ内のどの場所を指すかを調整する dynamic_cast です。

dynamic_cast<A*>(b)->a()

jeffamaphone が述べたように、2 つのクラスのレイアウトが似ているために、間違った関数が呼び出されます。

于 2010-01-20T22:04:26.813 に答える
1

C スタイルのキャスト (または C++ の同等の reinterpret_cast<>) を使用することが正当化または要求される C++ の機会はほとんどありません。2 つのうちの 1 つを使用したくなるときはいつでも、コードや設計を疑ってください。

于 2010-01-20T22:08:23.587 に答える
0

B*からへのキャストに微妙なバグがあると思いますがA*、動作は未定義です。C スタイルのキャストの使用を避け、C++ キャストを優先します (この場合はdynamic_cast. コンパイラがデータ型と vtable エントリのストレージをレイアウトした方法が原因で、別の関数のアドレスを見つけることになりました。

于 2010-01-20T22:02:53.370 に答える