C++ 標準によって、単一継承によってオブジェクトが上向きに「成長」することが保証されclass Base
ているのだろうか。class Derived: public Base
Derived* ptr
dynamic_cast<Base*>(ptr)
ptr
2 に答える
いいえ。メモリレイアウトは実装の詳細です。
そうは言っても、仮想関数がないと仮定して、実際にそれを行わない実装を私は知りません。に仮想関数を導入した場合Derived
(ただし、に仮想関数はありませんでしたBase
)、仮想テーブルポインタを(実装に応じて)Base
フィールドの前に配置できます(Base*
より大きい値になりますDerived*
)。
明確化:
上記の例は、VisualC++固有のものです。次のコードを使用してチェックアウトできます。
class Base {
int X;
};
class Derived : public Base {
virtual void f() {
}
int Y;
};
int main() {
Derived d;
Derived* d_ptr = &d;
Base* b_ptr = dynamic_cast<Base*>(d_ptr); // static_cast would be enough BTW.
bool base_smaller_or_equal = (ptrdiff_t)b_ptr <= (ptrdiff_t)d_ptr;
return 0;
}
VisualC++の下にbase_smaller_or_equal
あります。false
@enobayramのコメントから判断するとtrue
、GCCの下にあるはずです。
いずれにせよ、これは実装の詳細であり、信頼できるものではありません。
いいえ。
しかし、まだ慌てる必要はありません。この種の質問は、標準 C++ では完全に対処されていませんが、コンパイラが従う ABI ドキュメントによって回答されています。gcc、Clang、icc ( VC++を除く) などの多くのコンパイラは、 Itanium ABIに従っています。
Itanium ABI では、単一の非仮想継承があり、Base クラスに仮想メソッドがある限り、Derived と Base は常に同じアドレスを持ちます。
そうは言っても、これは実際に心配する必要のない実装です。C++ 標準に準拠したコードを完全に記述しながら、ユース ケースを管理できます。問題は、C++ では任意のポインターをvoid*
andにキャストしてchar*
(後者は非エイリアシングの特定の例外として)、その逆にキャストできることです。心配する必要がある唯一のことは、 aBase*
を a にケース化するとき、 aではなく avoid*
にキャストする必要があるということです。つまり、入力した型と返される型が一致する必要があります。Base*
Derived*
ただし、オブジェクトのサイズを (確かに) 知ることははるかに困難です。これにはsizeof
、オブジェクトの現在の動的タイプへの適用が必要であり、それを取得する機能はありませんvirtually
。
私のアドバイスは、実際に基本クラスをインストルメント化することです。
class Base {
public:
char const* address() const { return (char const*)dynamic_cast<void const*>(this); }
size_t offset() const { return this->address() - (char const*)this; }
virtual size_t size() const { return sizeof(Base); } // to be overriden
virtual ~Base() {}
};
これは、必要なすべての情報を取得するのに役立ちます (デモを参照)。Itanium ABIではoffset()
常に が返されることに注意してください。0
ただし、少なくともここでは想定していません。