サブクラスアドレスは仮想基本クラスアドレスと同じですか?


8

単純な単一継承を使用する場合、派生クラスのアドレスは基本クラスのアドレスと同じであることは誰もが知っています。多重継承はそれを真実にしません。

仮想継承もそれを正しくありませんか?つまり、次のコードは正しいですか。

struct A {};

struct B : virtual A
{
    int i;
};

int main()
{
    A* a = new B; // implicit upcast
    B* b = reinterpret_cast<B*>(a); // fishy?
    b->i = 0;

    return 0;
}

1
reinterpret_castクラスの場合は常に怪しげです(クラスからvoid*、および同じクラスに戻ることを除く)。
ハイド

3
「単純な単一継承を使用する場合、派生クラスのアドレスは基本クラスのアドレスと同じであることは誰もが知っています」というのは非常に強力な声明です。あなたは必ず標準保証これを?
ハイド

7
「私たち全員がそれを知っている」で始まる文が真実ではないことは、人間の言語の興味深い癖です。
molbdnilo

5
「C ++は、標準が保証するものにのみ依存している場合、かなり非現実的な言語になるでしょう」私は、他の人がそうであるように数十年にわたってC ++を開発していませんが、標準に違反したり、アプリケーションの未指定/未定義の動作に依存したりする必要はありませんでした。
ティモ

2
@ user1610015最初の文は一部の主要なコンパイラでは常に正しいとは限らず、C ++標準ではそうではないため、特定の場合にそれを保証する他の仕様(特定のコンパイラまたはABIに関する)が必要です。
OO Tiib

回答:


5

単純な単一継承を使用する場合、派生クラスのアドレスは基本クラスのアドレスと同じであることは誰もが知っています。

その主張は真実ではないと思います。以下のコードでは、単純な(仮想ではなく)単一の(複数ではない)継承がありますが、アドレスは異なります。

class A
{
public:
   int getX()
   {
      return 0;
   }
};

class B : public A
{
public:
   virtual int getY()
   {
      return 0;
   }
};

int main()
{
   B b;
   B* pB = &b;

   //A* pA = dynamic_cast<A*>(pB);
   A* pA = static_cast<A*>(pB);

   std::cout << "The address of pA is: " << pA << std::endl;
   std::cout << "The address of pB is: " << pB << std::endl;

   return 0;
}

VS2015の出力は次のとおりです。

The address of pA is: 006FF8F0
The address of pB is: 006FF8EC

仮想継承もそれを正しくありませんか?

上記のコードの継承を仮想に変更すると、結果は同じになります。したがって、仮想継承の場合でも、ベースオブジェクトと派生オブジェクトのアドレスは異なる場合があります。


実際G ++は、ビット変更されたコードを使用してケースを確認:coliru.stacked-crooked.com/a/ccea741b7126ee8a
OO Tiib

@ÖöTiib void main()は、最新のMSVSコンパイラでも使用できます。ところで、コメントをありがとう。コードを更新しました。
グプタ

1
いいえ、void main()受け入れられません。それはint main()標準に従っている必要があります。そしてdynamic_cast、それをコードから削除してください、それはそこには必要ありません、そしてそれは混乱を引き起こします。
geza

2

の結果は、サブオブジェクトと囲んでいるオブジェクトがポインタ相互変換可能である場合にreinterpret_cast<B*>(a);のみ、囲んでいるBオブジェクトを指すことが保証されています。C++ 17標準の[expr.static.cast] / 3を参照してください。aaB

派生クラスオブジェクトは、派生オブジェクトがstandard-layoutであり、直接の非静的データメンバーがなく、基本クラスオブジェクトがその最初の基本クラスサブオブジェクトである場合にのみ、基本クラスオブジェクトとポインター相互変換できます。[basic.compound] /4.3

有するvirtual基本クラスすることからクラスを不適格標準レイアウト[クラス] /7.2

したがって、Bは仮想基本クラスと非静的データメンバーを持っているためb、囲んでいるBオブジェクトを指しませんが、代わりにbのポインター値はから変更されませんa

オブジェクトをi指しているかのようにメンバーにアクセスすると、B動作が未定義になります。

その他の保証は、特定のABIまたはその他の仕様に基づいています。


2

多重継承はそれを真実にしません。

それは完全に正しいわけではありません。この例を考えてみましょう:

struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};

のインスタンスを作成するときDBおよびCはそれぞれのそれぞれのインスタンスでインスタンス化されますA。ただし、のインスタンスがのDインスタンスBとのそれぞれのインスタンスのアドレスが同じであれば問題はありませんA。必須ではありませんが、clang 11andでコンパイルすると、これが正確に起こり gcc 10ます。

D: 0x7fffe08b4758 // address of instance of D
B: 0x7fffe08b4758 and A: 0x7fffe08b4758 // same address for B and A
C: 0x7fffe08b4760 and A: 0x7fffe08b4760 // other address for C and A

仮想継承もそれを真実にしませんか

上記の例の修正版を考えてみましょう:

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};

virtual関数指定子の使用は通常、あいまいな関数呼び出しを回避するために使用されます。したがって、virtual継承を使用する場合はBCインスタンスとインスタンスの両方で共通のAインスタンスを作成する必要があります。をインスタンス化Dすると、次のアドレスが取得されます。

D: 0x7ffc164eefd0
B: 0x7ffc164eefd0 and A: 0x7ffc164eefd0 // again, address of A and B = address of D
C: 0x7ffc164eefd8 and A: 0x7ffc164eefd0 // A has the same address as before (common instance)

次のコードは正しいですか

ここでを使用する理由はありませんreinterpret_cast。さらに、未定義の動作が発生します。static_cast代わりに使用:

A* pA = static_cast<A*>(pB);

この例では、両方のキャストの動作が異なります。reinterpret_cast再解釈されますpBへのポインタとしてA、しかしポインタはpA上記の例(A対C)のように、異なるアドレスを指すことができます。を使用すると、ポインタは正しくアップキャストされますstatic_cast


-2

理由abするので、ので、あなたのケースで異なっているがあるA任意の仮想メソッドを持っていない、A維持されていませんvtable。一方、Bは維持しvtableます。

にアップキャストするAと、コンパイラーは十分に賢くなり、のvtable意味をスキップしBます。したがって、アドレスの違い。にreinterpret_cast戻るべきBではありません。それは機能しません。

私の主張を確認するには、追加してみてくださいvirtual方法を、言うvirtual void foo() {}class A。今Aも維持しvtableます。したがってreinterpret_cast、Bへのdowncast()は元のを返しますb


vtablesはここでは関係ありません。
mfnx

@mfnx OPの質問Subclass address equal to virtual base class address? は、すべて仮想継承に関係しています。また、仮想継承はすべてvtableと関係があります。
theWiseBro

@walnut OPの例は、仮想継承を実行します。キャストが誤った結果をもたらすのは、クラスAにvtableがないためです。これは行われるべきではなく、違法であることは事実ですが、実用的にしましょう。
theWiseBro

@gezaはい、私の愚かなコメントを申し訳ありません。それでも、仮想メソッドを追加Aすることで、アドレスが一致し、キャストが理論上または実際に機能することが保証されるかどうかは疑問です。投稿がロックされているため、投稿の編集なしでは投票を削除できません。
クルミ

「aとbが異なる理由...」:同じアドレスを共有している可能性があります。vtableがあるかどうかは、同じアドレスではないという意味ではありません。VS2015の@guptaと比較したclangとgccの違いに注意してください。
mfnx
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.