未定義の動作、メモリリーク、またはその他の正しいコードセットでのクラッシュを引き起こす可能性のあるC ++オブジェクトスライス効果の例はありますか?たとえば、クラスA
とB
(から継承されたA
)は正しくて健全であるが、明らかに呼び出すvoid f(A a)
と厄介なことが発生します。
テスト問題を作成するために必要です。目標は、参加者がスライス現象を認識しているかどうかを知ることです。その正しさは意見の問題であってはならないサンプルコードスニペットを使用します。
未定義の動作、メモリリーク、またはその他の正しいコードセットでのクラッシュを引き起こす可能性のあるC ++オブジェクトスライス効果の例はありますか?たとえば、クラスA
とB
(から継承されたA
)は正しくて健全であるが、明らかに呼び出すvoid f(A a)
と厄介なことが発生します。
テスト問題を作成するために必要です。目標は、参加者がスライス現象を認識しているかどうかを知ることです。その正しさは意見の問題であってはならないサンプルコードスニペットを使用します。
が実際に「正しくて健全」である場合A
、スライス(ベースサブオブジェクトのコピー)は明確に定義されており、言及した問題のいずれも発生しません。コピーがのように動作することを期待している場合、それが引き起こす唯一の問題は予期しない動作ですB
。
が正しくコピーできない場合A
、スライスすると、そのタイプのオブジェクトのコピーで問題が発生します。たとえば、オブジェクトが保持するポインタを削除するデストラクタがあり、コピーすると同じものへの新しいポインタが作成される場合、両方のデストラクタが同じポインタを削除すると、未定義の動作が発生します。これは、スライス自体の問題ではありませんが、スライスされたオブジェクトの無効なコピーセマンティクスでは問題になります。
オブジェクトのスライスが実際に問題になるのは、基本クラスへのポインターまたは参照を介して派生クラスを操作する場合のみです。その後、派生クラスの追加データは変更されませんが、基本部分の追加データは変更される可能性があります。これにより、派生クラスの不変条件が壊れる可能性があります。簡単な例については、http://en.wikipedia.org/wiki/Object_slicingを参照してください。
ただし、これは(派生クラスの)設計上の欠陥と考えます。基底クラスへのポインターまたは参照引数を取得したメソッドを含む、正当なメソッドでクラスにアクセスすると、その不変条件が壊れる可能性がある場合、そのクラスは正しく設計されていません。これを回避する 1 つの方法は、 base を宣言しprivate
て、派生クラスがその base 経由で合法的にアクセスできないようにすることです。
例は次のとおりです。
class A
{
int X;
A(int x) : X(x) {}
void doubleX() { X+=X; }
/* ... */
};
class B : public A
{
int X_square;
B(int x) : A(x), X_square(x*x) {}
/* ... */
};
B b(3);
B.doubleX(); /// B.X = 6 but B.X_square=9
この例から、これが B の単純な設計上の欠陥であることも明らかです。この例では、
void B::doubleX() { A::doubleX(); X_squared=X*X; }
として、問題を解決しません。
A&a=b;
a.doubleX();
それでも不変条件を破ります。ここでの唯一の解決策は、ベースをA
プライベートに宣言するか、より良い方法として、A
のベースではなくプライベート メンバーを作成することですB
。
このような例はいつでも作成できます
struct A {
A() : invariant(true) {}
virtual void do_sth() { assert(invariant); }
protected:
bool invariant;
};
struct B : A {
B() { invariant=false; }
virtual void do_sth() { }
};
void f(A a)
{
a.do_sth();
}
もちろん、A
コピー コンストラクター/代入演算子が不変式が true かどうかをチェックしない場合、これは内部で防止できます。
不変式がブール値よりも暗黙的である場合、これらのことは非常にわかりにくい場合があります。