web-dev-qa-db-ja.com

オブジェクトスライスとは

スライスの問題としてIRCの中で誰かがそれを述べました。

678
Frankomania

「スライス」とは、派生クラスのオブジェクトを基本クラスのインスタンスに割り当てることで、情報の一部が失われることです。その一部は「スライス」されます。

例えば、

class A {
   int foo;
};

class B : public A {
   int bar;
};

そのため、B型のオブジェクトには、foobarの2つのデータメンバーがあります。

あなたがこれを書くとしたら:

B b;

A a = b;

その後、メンバーbに関するbar内の情報がa内で失われます。

565
David Dibben

ここでのほとんどの回答では、スライスの実際の問題を説明できません。彼らは、危険なケースではなく、スライスの良性のケースのみを説明します。他の回答と同様に、2つのクラスABを扱っていると仮定します。ここで、BAから(公に)派生します。

この状況では、C++を使用すると、BのインスタンスをAの代入演算子(およびコピーコンストラクター)に渡すことができます。これは、Bのインスタンスを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のコピーになると思うかもしれません。しかし、悲しいかな、それはnot!です!調べてみると、b2b1のいくつかのチャンク(BAから継承するチャンク)といくつかのb2のチャンク(Bのみが含むチャンク)。痛い!

何が起こった?さて、C++はデフォルトで割り当て演算子をvirtualとして扱いません。したがって、行a_ref = b1は、Aの代入演算子ではなく、Bの代入演算子を呼び出します。これは、非仮想関数の場合、declared type(これはA&)がactual type(これはa_refBのインスタンスを参照するため、Bである。 Aの代入演算子は、明らかにAで宣言されたメンバーのみを知っているため、Bに追加されたメンバーは変更せずに、それらのみをコピーします。

解決策

通常、オブジェクトの一部のみに割り当てることはほとんど意味がありませんが、残念ながらC++はこれを禁止する組み込みの方法を提供しません。ただし、独自にロールすることはできます。最初のステップは、代入演算子virtualを作成することです。これにより、宣言済みタイプではなく、常に実際のタイプの代入演算子が呼び出されることが保証されます。 2番目のステップは、dynamic_castを使用して、割り当てられたオブジェクトに互換性のある型があることを確認することです。 Bassign()はおそらくAassign()を使用してコピーするので、3番目のステップは(保護された!)メンバー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
  }
};

純粋に便宜上、Boperator=は、Bのインスタンスを返すことをknowsであるため、戻り型を共変的にオーバーライドすることに注意してください。

460
fgp

基本クラスAと派生クラスBがある場合は、次のことができます。

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

メソッドwantAnAderivedのコピーを必要とします。ただし、クラスderivedは、その基本クラスBにない追加のメンバー変数を作成する可能性があるため、オブジェクトAは完全にはコピーできません。

したがって、wantAnAを呼び出すために、コンパイラは派生クラスのすべての追加メンバを「切り捨て」ます。結果は、作成したくないオブジェクトになる可能性があります。

  • それは不完全かもしれません、
  • これはAオブジェクトのように振る舞います(クラスBの特別な振る舞いはすべて失われます)。
146
Black

これらはすべて良い答えです。オブジェクトを値と参照で渡すときの実行例を追加したいと思います。

#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'
36
geh

"C++スライス"のためのグーグルでの3番目のマッチは私にこのウィキペディアの記事 http://en.wikipedia.org/wiki/Object_slicing とこれを与えますしかし、最初の数投稿で問題が定義されています): http://bytes.com/forum/thread163565.html

つまり、サブクラスのオブジェクトをスーパークラスに割り当てるときです。スーパークラスはサブクラス内の追加情報を何も知りませんし、それを保管するスペースがないため、追加情報は「切り取られ」ます。

これらのリンクで「良い答え」を得るのに十分な情報が得られない場合は、質問を編集して、探しているものをお知らせください。

31

スライスの問題は、メモリの破損を招く可能性があるため深刻であり、プログラムに問題がないことを保証することは非常に困難です。それを言語の外で設計するためには、継承をサポートするクラスは参照だけでアクセス可能であるべきです(値ではない)。 Dプログラミング言語はこの特性を持っています。

クラスAと、Aから派生したクラスBについて考えてみましょう。A部分にポインターpと、pがBの追加データを指すBインスタンスがあると、メモリーが破損する可能性があります。その後、追加データが切り取られると、pはゴミを指しています。

28
Walter Bright

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
}

派生クラスオブジェクトが基本クラスオブジェクトに割り当てられ、派生クラスオブジェクトの追加の属性が基本クラスオブジェクトを形成するためにスライスされるとき、オブジェクトスライスが発生します。

9

それで...派生情報を失うのはなぜ悪いのでしょうか。これは、派生クラスの作成者が、余分な情報を切り取ってオブジェクトが表す値を変更するように表現を変更した可能性があるためです。派生クラスが特定の操作にとってより効率的な表現をキャッシュするために使用されているが、基本表現に変換して戻るのに費用がかかる場合、これが発生する可能性があります。

また、誰かがスライスを避けるために何をすべきかについても言及するべきだと考えました... C++コーディング規約、101のルールガイドライン、そしてベストプラクティスのコピーを入手してください。スライス処理は#54です。

プロテクトコピーコンストラクタ、プロテクトされた純粋な仮想DoClone、および(さらに)派生クラスがDoCloneを正しく実装できなかったかどうかを示すアサート付きのパブリックCloneを用意するという、この問題を完全に処理するためのやや複雑なパターンを提案します。 (Cloneメソッドは多相オブジェクトの適切なディープコピーを作成します。)

必要に応じて明示的なスライスを可能にするベース明示的でコピーコンストラクタをマークすることもできます。

7
Steve Steiner

C++におけるスライスの問題は、そのオブジェクトの値のセマンティクスから生じます。これは、主にCの構造体との互換性が原因で残っています。オブジェクトを実行する他のほとんどの言語で見られる「通常の」オブジェクトの振る舞いを実現するには、明示的な参照またはポインタ構文を使用する必要があります。つまり、オブジェクトは常に参照によって渡されます。

簡単な答えは、派生オブジェクトを基本オブジェクトby valueに割り当てることによってオブジェクトをスライスすることです。つまり、残りのオブジェクトは派生オブジェクトの一部にすぎません。値のセマンティクスを維持するために、スライスは合理的な動作であり、比較的まれな用途があります。これは他のほとんどの言語には存在しません。それをC++の機能と見なす人もいれば、C++の風変わりな機能の1つと見なす人もいます。

7
ididak

1。スライシング問題の定義

Dが基本クラスBの派生クラスである場合は、Derived型のオブジェクトをBase型の変数(またはパラメータ)に代入できます。

EXAMPLE

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。スライシング問題の解決方法

問題を克服するために、動的変数へのポインタを使用します。

EXAMPLE

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

この場合、ptrD(子孫クラスオブジェクト)が指す動的変数のデータメンバまたはメンバ関数は失われません。さらに、関数を使用する必要がある場合は、その関数は仮想関数でなければなりません。

7
haberdar

スライシングはあなた自身のクラスやプログラムがうまく設計/設計されていないとき以外にはそれほど問題ではないようです。

サブクラスオブジェクトをスーパークラス型のパラメータをとるメソッドへのパラメータとして渡す場合、それを確実に認識し、内部的には知っている必要があります。呼び出されたメソッドはスーパークラス(別名ベースクラス)オブジェクトのみを処理します。

基本クラスが要求されているサブクラスを提供すると、どういうわけかサブクラス固有の結果になり、スライスが問題になるというのは不当な期待だけです。メソッドの使用における設計の不備、またはサブクラスの実装の不備。私は通常、優れたOOPデザインを犠牲にした結果として、便宜性やパフォーマンスが向上したと考えています。

4
Minok

スライスとは、サブクラスのオブジェクトが値によって、または基本クラスオブジェクトを期待する関数から渡されたり返されたりするときに、サブクラスによって追加されたデータが破棄されることを意味します。

説明:次のクラス宣言を考えてください。

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

基本クラスのコピー関数は派生について何も知らないので、派生の基本部分だけがコピーされます。これは一般にスライスと呼ばれます。

3
Santosh

オブジェクトスライシングを説明する多くの記事を読んだ後で試してみますが、それがどのように問題になるかについては触れません。

メモリ破損を引き起こす可能性がある悪質なシナリオは次のとおりです。

  • クラスは、(誤って、おそらくコンパイラによって生成された)多態性基本クラスへの代入を提供します。
  • クライアントは派生クラスのインスタンスをコピーしてスライスします。
  • クライアントは、スライスオフ状態にアクセスする仮想メンバー関数を呼び出します。
3
Dude
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; 
}
2
quidkid