回答:
「スライス」とは、派生クラスのオブジェクトを基本クラスのインスタンスに割り当てて、情報の一部を失うことです。その一部は「スライス」されます。
例えば、
class A {
int foo;
};
class B : public A {
int bar;
};
したがって、型のオブジェクトにB
は2つのデータメンバーとがfoo
ありbar
ます。
次に、これを書くとしたら:
B b;
A a = b;
次に、b
メンバーに関する情報bar
がで失われa
ます。
A a = b;
a
タイプのオブジェクトA
ですB::foo
。今キャストバックするのは間違いだと思います。
B b1; B b2; A& b2_ref = b2; b2 = b1
。にコピーb1
したと思われるかもしれませんb2
が、そうではありません!あなたはコピーした部分のb1
へb2
(の一部b1
それB
から継承するA
)、および他の部分の左b2
変わらないが。b2
は、数ビットのb1
後にいくつかのチャンクが続くフランケンシュタインの生き物ですb2
。うわっ!答えは非常に誤解を招くと思うので、反対投票。
B b1; B b2; A& b2_ref = b2; b2_ref = b1
「実際の問題が発生するのは、」であるはずです...非仮想代入演算子を持つクラスから派生した場合。されてA
も、導出するためのもの?仮想機能はありません。タイプから派生する場合、そのメンバー関数を呼び出すことができるという事実に対処する必要があります。
ここでのほとんどの回答は、スライスの実際の問題が何であるかを説明できません。彼らはスライスの良性のケースのみを説明し、危険なケースは説明しません。あなたは二つのクラスを扱っていることを、他の回答のように、想定A
してB
、どこB
から(公的)を導出しますA
。
このような状況では、C ++を使用すると、インスタンス渡すことができますB
へ A
の代入演算子(ともコピーコンストラクタへの)。なぜなら、インスタンスのこの作品B
Aに変換することができますconst A&
。これは、代入演算子とコピーコンストラクターが引数を期待しているものです。
B b;
A a = b;
そこでは何も悪いことが起こりません-あなたA
はのコピーであるインスタンスを要求しましたB
、そしてそれはまさにあなたが得るものです。確かに、a
一部b
ののメンバーは含まれませんが、どのようにすべきですか?それはだA
、すべての後に、ではないB
ので、それもしていない聞きましたこれらのメンバーについて、それらを格納することができるでしょうおろか。
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!
それb2
はb1
後でコピーになると思うかもしれません。しかし、悲しいかな、そうではありません!調べてみると、それb2
はフランケンシュタインの生き物であり、b1
(B
から継承するチャンクA
)のいくつかのチャンクと、b2
(から継承するチャンク)のいくつかのチャンクでできていることがわかりますB
含むます。痛い!
どうした?まあ、デフォルトではC ++は代入演算子をとして扱いませんvirtual
。したがって、行a_ref = b1
はの代入演算子でA
はなくの代入演算子を呼び出しますB
。なぜなら、非仮想関数のため、これは宣言:(正式に静的タイプ(ある)A&
とは対照的に、呼び出される関数決定する)実際:(正式に動的(あろう型)B
、以降a_ref
の参照のインスタンスB
) 。現在、A
の代入演算子はで宣言されたメンバーのみを認識しているA
ため、それらのみがコピーされ、追加されたメンバーはB
変更されません。
オブジェクトの一部にのみ割り当てることは、通常ほとんど意味がありませんが、残念ながら、C ++には、これを禁止する組み込みの方法がありません。ただし、自分でロールすることはできます。最初のステップは、代入演算子をvirtualにすることです。これにより、宣言された型ではなく、常に実際の型の代入演算子が呼び出されることが保証されます。2番目のステップでは、を使用して、割り当てられたオブジェクトに互換性のあるタイプがあることを確認します。第3のステップは(保護された!)メンバーの実際の割り当てを行うことですから、のは、おそらく使用したいと思うでしょう'S コピーするには、のメンバー。dynamic_cast
assign()
B
assign()
A
assign()
A
class A {
public:
virtual A& operator= (const A& a) {
assign(a);
return *this;
}
protected:
void assign(const A& a) {
// copy members of A from a to this
}
};
class B : public A {
public:
virtual B& operator= (const A& a) {
if (const B* b = dynamic_cast<const B*>(&a))
assign(*b);
else
throw bad_assignment();
return *this;
}
protected:
void assign(const B& b) {
A::assign(b); // Let A's assign() copy members of A from b to this
// copy members of B from b to this
}
};
純粋に便宜上、B
のインスタンスを返すoperator=
ことがわかっているため、は共変的に戻り値の型をオーバーライドすることに注意してくださいB
。
derived
値を期待するコードに任意の値を与えるかbase
、または任意の派生参照をベース参照として使用できます。両方の概念に個別に対応する型システムを持つ言語が欲しいです。派生参照をベース参照の代わりに使用できる場合が多くありますが、派生インスタンスはベース参照の代わりに使用できません。インスタンスは変換可能でなければならないが、参照で代用すべきでない場合も多くあります。
基本クラスA
と派生クラスがある場合B
、次のことができます。
void wantAnA(A myA)
{
// work with myA
}
B derived;
// work with the object "derived"
wantAnA(derived);
ここで、メソッドにwantAnA
はのコピーが必要ですderived
。ただし、derived
クラスB
がその基本クラスにない追加のメンバー変数を作成する可能性があるため、オブジェクトを完全にコピーすることはできませんA
。
したがって、を呼び出すために、wantAnA
コンパイラーは派生クラスのすべての追加メンバーを「スライス」します。結果は、作成したくないオブジェクトになる可能性があります。
A
-objectのように動作します(クラスのすべての特別な動作B
は失われます)。wantAnA
(その名前が示すように!)望んでいるA
、それはそれを取得するものです。そして、のインスタンスA
は、ええと、のように動作しA
ます。それはどのように意外ですか?
derived
型に実行する自動キャストにありますA
。暗黙的なキャストは常に、C ++で予期しない動作の原因となります。これは、キャストが行われたことをコードをローカルで見るだけでは理解しにくいことが多いためです。
これらはすべて良い答えです。オブジェクトを値渡しまたは参照渡しする場合の実行例を追加したいと思います。
#include <iostream>
using namespace std;
// Base class
class A {
public:
A() {}
A(const A& a) {
cout << "'A' copy constructor" << endl;
}
virtual void run() const { cout << "I am an 'A'" << endl; }
};
// Derived class
class B: public A {
public:
B():A() {}
B(const B& a):A(a) {
cout << "'B' copy constructor" << endl;
}
virtual void run() const { cout << "I am a 'B'" << endl; }
};
void g(const A & a) {
a.run();
}
void h(const A a) {
a.run();
}
int main() {
cout << "Call by reference" << endl;
g(B());
cout << endl << "Call by copy" << endl;
h(B());
}
出力は次のとおりです。
Call by reference
I am a 'B'
Call by copy
'A' copy constructor
I am an 'A'
「C ++スライス」のためのGoogleでの第三試合は私にこのWikipediaの記事与えhttp://en.wikipedia.org/wiki/Object_slicingを、これは(加熱しますが、最初のいくつかの記事では、問題を定義する):http://bytes.com/ forum / thread163565.html
つまり、サブクラスのオブジェクトをスーパークラスに割り当てるときです。スーパークラスは、サブクラスの追加情報を何も認識しておらず、それを格納する領域がないため、追加情報は「スライス」されます。
これらのリンクで「適切な回答」を得るのに十分な情報が得られない場合は、質問を編集して、探している情報をさらにお知らせください。
スライスの問題はメモリ破損を引き起こす可能性があるため深刻であり、プログラムがその影響を受けないことを保証することは非常に困難です。言語からそれを設計するには、継承をサポートするクラスは参照によってのみ(値ではなく)アクセスできる必要があります。Dプログラミング言語にはこの特性があります。
クラスA、およびAから派生したクラスBについて考えてみます。A部分にポインターpと、pがBの追加データを指すBインスタンスがある場合、メモリー破損が発生する可能性があります。次に、追加データが切り捨てられると、pはガベージを指しています。
Derived
暗黙的にに変換可能であるため、見落とされた派生クラスは、基本クラスのコピーctorによってピックアップされる可能性がありBase
ます。)これは明らかに開閉原理に逆らい、メンテナンスの負担が大きくなります。
C ++では、派生クラスオブジェクトを基本クラスオブジェクトに割り当てることができますが、他の方法は不可能です。
class Base { int x, y; };
class Derived : public Base { int z, w; };
int main()
{
Derived d;
Base b = d; // Object Slicing, z and w of d are sliced off
}
オブジェクトのスライスは、派生クラスオブジェクトが基本クラスオブジェクトに割り当てられるときに発生します。派生クラスオブジェクトの追加の属性は、基本クラスオブジェクトを形成するために切り取られます。
C ++でのスライスの問題は、そのオブジェクトの値のセマンティクスから発生しますが、これは主にCの構造体との互換性が原因です。オブジェクトを実行する他のほとんどの言語で見られる「通常の」オブジェクト動作を実現するには、明示的な参照またはポインタ構文を使用する必要があります。つまり、オブジェクトは常に参照によって渡されます。
簡単に言えば、派生オブジェクトを基本オブジェクトに値で割り当てることによってオブジェクトをスライスするということです。つまり、残りのオブジェクトは派生オブジェクトの一部にすぎません。値のセマンティクスを維持するために、スライスは妥当な動作であり、他のほとんどの言語には存在しない比較的まれな用途があります。一部の人々はそれをC ++の機能であると考えていますが、多くの人はそれをC ++の奇妙な機能/誤機能の1つであると考えています。
struct
、互換性、またはその他の非感が任意のランダムなOOPの司祭はあなたに言いました。
Base
正確にsizeof(Base)
バイトを取得する必要があるため、これは通常のスタックコピー動作です。)派生クラスのメンバーはコピーされません。オフセットはsizeofの外にあります。「データの損失」を回避するには、他の人と同じようにポインタを使用します。ポインタメモリは所定の位置とサイズに固定されていますが、スタックは非常に不安定です
それで...なぜ派生した情報を失うことが悪いのですか?...派生クラスの作成者が表現を変更して、余分な情報を切り取ってオブジェクトが表す値を変更した可能性があるためです。これは、派生クラスを使用して特定の操作でより効率的な表現をキャッシュする場合に発生する可能性がありますが、基本表現に変換し直すにはコストがかかります。
また、誰かがスライスを避けるために何をすべきかについても言及する必要があると考えました... C ++コーディング標準、101のルールガイドライン、およびベストプラクティスのコピーを入手してください。スライスの扱いは#54です。
これは、問題を完全に処理するために多少洗練されたパターンを提案します:保護されたコピーコンストラクター、保護された純粋な仮想DoClone、および(さらに)派生クラスがDoCloneを正しく実装できなかったかどうかを通知するアサート付きのパブリックCloneがあります。(Cloneメソッドは、ポリモーフィックオブジェクトの適切な深いコピーを作成します。)
必要に応じて明示的にスライスできるように、ベースコンストラクタでコピーコンストラクタをマークすることもできます。
1.スライスの問題の定義
Dが基本クラスBの派生クラスである場合、Derived型のオブジェクトをBase型の変数(またはパラメーター)に割り当てることができます。
例
class Pet
{
public:
string name;
};
class Dog : public Pet
{
public:
string breed;
};
int main()
{
Dog dog;
Pet pet;
dog.name = "Tommy";
dog.breed = "Kangal Dog";
pet = dog;
cout << pet.breed; //ERROR
上記の割り当ては許可されていますが、変数petに割り当てられた値はブリードフィールドを失います。これはスライス問題と呼ばれます。
2.スライスの問題を修正する方法
この問題を解決するには、動的変数へのポインターを使用します。
例
Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed;
この場合、ptrD(子孫クラスオブジェクト)によってポイントされている動的変数のデータメンバーまたはメンバー関数は失われません。また、関数を使用する必要がある場合、関数は仮想関数でなければなりません。
dog
クラスの一部ではない状態Pet
(breed
データメンバー)が変数にコピーされないという問題はどうしてpet
ですか?コードはPet
データメンバーのみに関係しています-どうやら。スライスは不要な場合は間違いなく「問題」ですが、ここではわかりません。
((Dog *)ptrP)
"使用をお勧めしますstatic_cast<Dog*>(ptrP)
Dog::breed
)のメンバーにアクセスしようとしても、SLICINGに関連するエラーにはならないことを理解していますか?
自分のクラスとプログラムが十分に構築/設計されていない場合を除いて、スライスはそれほど問題ではないように思えます。
スーパークラスタイプのパラメーターを取るメソッドにパラメーターとしてサブクラスオブジェクトを渡す場合、それを認識し、内部的に知っておく必要があります。呼び出されたメソッドは、スーパークラス(別名ベースクラス)オブジェクトのみで機能します。
ベースクラスがリクエストされた場所にサブクラスを提供すると、どういうわけかサブクラス固有の結果が生じ、スライスが問題になるという不合理な期待だけに思えます。メソッドの使用に関する設計が不十分であるか、サブクラスの実装が不十分です。私は通常、適切なOOP設計を犠牲にして便宜またはパフォーマンスの向上を優先した結果だと思います。
スライスとは、サブクラスのオブジェクトが値によって、または基本クラスオブジェクトを必要とする関数から渡されたり返されたりすると、サブクラスによって追加されたデータが破棄されることを意味します。
説明: 次のクラス宣言を検討してください。
class baseclass
{
...
baseclass & operator =(const baseclass&);
baseclass(const baseclass&);
}
void function( )
{
baseclass obj1=m;
obj1=m;
}
ベースクラスのコピー関数は派生について何も知らないので、派生のベース部分のみがコピーされます。これは一般にスライスと呼ばれます。
class A
{
int x;
};
class B
{
B( ) : x(1), c('a') { }
int x;
char c;
};
int main( )
{
A a;
B b;
a = b; // b.c == 'a' is "sliced" off
return 0;
}
派生クラスオブジェクトが基本クラスオブジェクトに割り当てられると、基本クラスに存在しないメンバーを除いて、派生クラスオブジェクトのすべてのメンバーが基本クラスオブジェクトにコピーされます。これらのメンバーは、コンパイラーによってスライスされます。これはオブジェクトスライスと呼ばれます。
次に例を示します。
#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
int a;
int b;
int c;
Base()
{
a=10;
b=20;
c=30;
}
};
class Derived : public Base
{
public:
int d;
int e;
Derived()
{
d=40;
e=50;
}
};
int main()
{
Derived d;
cout<<d.a<<"\n";
cout<<d.b<<"\n";
cout<<d.c<<"\n";
cout<<d.d<<"\n";
cout<<d.e<<"\n";
Base b = d;
cout<<b.a<<"\n";
cout<<b.b<<"\n";
cout<<b.c<<"\n";
cout<<b.d<<"\n";
cout<<b.e<<"\n";
return 0;
}
それは生成されます:
[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
私はスライスの問題に遭遇し、すぐにここに着陸しました。これに2セントを加算します。
「量産コード」(またはそれに近いもの)の例を見てみましょう。
アクションをディスパッチするものがあるとしましょう。たとえば、コントロールセンターのUI。
このUIは、現在ディスパッチ可能なもののリストを取得する必要があります。したがって、ディスパッチ情報を含むクラスを定義します。それを呼びましょうAction
。したがって、Action
いくつかのメンバー変数があります。簡単にするために、a std::string name
とa である2を使用していstd::function<void()> f
ます。次に、void activate()
それを実行するだけのf
メンバーをです。
したがって、UIはstd::vector<Action>
提供されます。次のようないくつかの関数を想像してみてください:
void push_back(Action toAdd);
これで、UIの観点から見た外観を確立しました。今のところ問題ありません。しかし、このプロジェクトに取り組んでいる他の人が突然、より多くの情報を必要とする特別なアクションがあると判断しましたAction
オブジェクトに。どんな理由で。これはラムダキャプチャでも解決できます。この例は、コードから1-1で取得されたものではありません。
その男はAction
、自分の味を追加するために派生しています。
彼は自作のクラスのインスタンスをpush_back
その後プログラムは混乱します。
どうしたの?
あなたがそうかもしれないように推測している:オブジェクトは、スライスされています。
インスタンスからの余分な情報が失われ、f
動作が未定義になる傾向があります。
この例が、A
sとB
が何らかの方法で派生していることについて話すとき、本当に物事を想像できない人々に光をもたらすことを願っています。