私が間違っているC++でスライスする?
-
21-12-2019 - |
質問
私はC++のスライスの問題について読んで、いくつかの例を試しました(私はJavaの背景から来ています)。残念ながら、私はいくつかの行動を理解していません。現在、私はこの例に固執しています(Efficent C++第3版からの代替例)。誰かが私にそれを理解するのを助けることができますか?
親の私の単純なクラス:
class Parent
{
public:
Parent(int type) { _type = type; }
virtual std::string getName() { return "Parent"; }
int getType() { return _type; }
private:
int _type;
};
子供の私の単純なクラス:
class Child : public Parent
{
public:
Child(void) : Parent(2) {};
virtual std::string getName() { return "Child"; }
std::string extraString() { return "Child extra string"; }
};
メイン:
void printNames(Parent p)
{
std::cout << "Name: " << p.getName() << std::endl;
if (p.getType() == 2)
{
Child & c = static_cast<Child&>(p);
std::cout << "Extra: " << c.extraString() << std::endl;
std::cout << "Name after cast: " << c.getName() << std::endl;
}
}
int main()
{
Parent p(1);
Child c;
printNames(p);
printNames(c);
}
私が得る実行をAfte:
名前:親
名前:親
エクストラ:子の余分な文字列
キャスト後の名前:親
それは「スライス」の原因であるため、私は、最初の2行を理解しています。しかし、なぜ静的キャストを介して子を親にキャストできるのか理解できません。この本には、スライスした後、すべての専門情報がスライスされると書かれています。だから私は推測する、私はキャストできません p に c, 、私は情報を持っていないので(関数printNamesの最初に、追加情報なしで新しい親オブジェクトが作成されます)。
さらに、なぜ私は"キャスト後の名前"を取得しているのですか:"キャスト後の名前"ではなく、キャストが成功した場合は"親":子供"?
解決
あなたの結果は不運の並外れた脳卒中です。これが私が得る結果です:
Name: Parent
Name: Parent
Extra: Child extra string
bash: line 8: 6391 Segmentation fault (core dumped) ./a.out
.
これはあなたのコードで何が起こっているのか:
c
をprintNames
に渡すと、変換が発生します。特に、パスは値によるものであるため、暗黙的に宣言されているParent
のコピーコンストラクタを呼び出し、そのコードがこのようになります。
Parent(Parent const& other) : _type{other._type} {}
.
それでも、_type
のc
変数をコピーし、他にはにコピーします。現在、Parent
型の new オブジェクトがあります(静的タイプと動的タイプの両方がParent
です)、c
は実際にはprintNames
にまったく渡されません。
関数内では、p
をChild&
に強制的に変換します。 p
は単にChild
ではなく、C ++では、C ++が診断されないため、C ++はあなたに診断されません(これは実際には恥が正しくないことがわかります)。< / P>
今、私たちは未定義の行動の国にあり、今すべてが起こることが許されます。実際には、Child::extraString
はthis
に(暗黙的または明示的に)アクセスされないため、その関数への呼び出しは成功するだけです。呼び出しは不正なオブジェクトで行われますが、オブジェクトは触れられないため、その機能はありません(まだ違法です)。
次の呼び出しは、Virtual Callで、一般的にChild::getName
にアクセスする必要があります(ほとんどの実装では、仮想メソッドテーブルポインタにアクセスします)。そしてやはりコードがUBであるため、何でも起こり得る。あなたは「ラッキー」で、コードは親クラスの仮想メソッドテーブルポインタをつかみました。私のコンパイラで、そのアクセスは明らかに失敗しました。
他のヒント
そのコードは恐ろしいです。何が起こっているのか:
printNames(c)
スライスc
, 、コピー構築ローカルp
からParent
呼び出し元のオブジェクトに埋め込まれたオブジェクc
オブジェクト、その後、設定p
'sへのポインタParent
仮想ディスパッチテーブル。のデータメンバーなので、
Parent
からコピーされたc
, 、タイプのp
は2であり、if
分岐が入力されますChild & c = static_cast<Child&>(p);
効果的にコンパイラに「私を信頼してください(私はプログラマーです)、私はそれを知っています」と伝えますp
実際にはChild
私が参照したいオブジェクト"ですが、それは露骨な嘘ですp
実際にはParent
からコピーされたオブジェクトChild
c
- 有効であるかどうかわからない場合は、コンパイラにこれを行うように依頼しないようにすることは、プログラマとしてあなたに責任があります
c.extraString()
それは知っているので、コンパイラによって静的に(コンパイル時に)発見されますc
である。Child
(またはさらに派生した型ですが、c.extraString
そうではありませんvirtual
したがって、静的に解決できます;それは 未定義の動作 これを行うにはParent
オブジェクトですが、おそらくextraString
データにアクセスしようとしません。Child
オブジェクトは持っているでしょう、それは表向きはあなたのために「ok」を実行していますc.getName()
はvirtual
, 、コンパイラはオブジェクトの仮想ディスパッチテーブルを使用します-オブジェクトは実際にはParent
これは動的に(実行時に)解決されます。Parent::getName
関数と関連する出力を生成します- ただし、仮想ディスパッチの実装は実装定義であり、未定義の動作は、すべてのc++実装で、またはすべての最適化レベルで、すべてのコンパイラオプ