C++ 什么是对象切片?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/274626/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:14:13  来源:igfitidea点击:

What is object slicing?

c++inheritancec++-faqobject-slicing

提问by Frankomania

Someone mentioned it in the IRC as the slicing problem.

有人在 IRC 中提到它是切片问题。

回答by David Dibben

"Slicing" is where you assign an object of a derived class to an instance of a base class, thereby losing part of the information - some of it is "sliced" away.

“切片”是将派生类的对象分配给基类的实例,从而丢失部分信息 - 其中一些信息被“切片”掉。

For example,

例如,

class A {
   int foo;
};

class B : public A {
   int bar;
};

So an object of type Bhas two data members, fooand bar.

所以一个类型的对象B有两个数据成员,foobar

Then if you were to write this:

那么如果你要写这个:

B b;

A a = b;

Then the information in babout member baris lost in a.

然后babout member 中的信息bar丢失在a.

回答by fgp

Most answers here fail to explain what the actual problem with slicing is. They only explain the benign cases of slicing, not the treacherous ones. Assume, like the other answers, that you're dealing with two classes Aand B, where Bderives (publicly) from A.

这里的大多数答案都无法解释切片的实际问题是什么。他们只解释了切片的良性案例,而不是危险的案例。假设,与其他答案一样,您正在处理两个类Aand B,其中B(公开)从A.

In this situation, C++ lets you pass an instance of Bto A's assignment operator (and also to the copy constructor). This works because an instance of Bcan be converted to a const A&, which is what assignment operators and copy-constructors expect their arguments to be.

在这种情况下,C++ 允许您传递Bto A的赋值运算符(以及复制构造函数)的实例。这是有效的,因为 的实例B可以转换为 a const A&,这就是赋值运算符和复制构造函数期望它们的参数是什么。

The benign case

良性案例

B b;
A a = b;

Nothing bad happens there - you asked for an instance of Awhich is a copy of B, and that's exactly what you get. Sure, awon't contain some of b's members, but how should it? It's an A, after all, not a B, so it hasn't even heardabout these members, let alone would be able to store them.

没有什么不好的事情发生 - 你要求一个实例,A它是 的副本B,这正是你得到的。当然,a不会包含某些b成员,但是应该如何呢?A毕竟是一个,不是一个B,所以它甚至没有听说过这些成员,更不用说能够存储它们了。

The treacherous case

奸诈的案子

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

You might think that b2will be a copy of b1afterward. But, alas, it's not! If you inspect it, you'll discover that b2is a Frankensteinian creature, made from some chunks of b1(the chunks that Binherits from A), and some chunks of b2(the chunks that only Bcontains). Ouch!

你可能认为那b2将是一个副本b1。但是,唉,它不是!如果你检查它,你会发现它b2是一个科学怪人的生物,由一些块b1B从 继承的块A)和一些块b2(只B包含的块)组成。哎哟!

What happened? Well, C++ by default doesn't treat assignment operators as virtual. Thus, the line a_ref = b1will call the assignment operator of A, not that of B. This is because, for non-virtual functions, the declared(formally: static) type (which is A&) determines which function is called, as opposed to the actual(formally: dynamic) type (which would be B, since a_refreferences an instance of B). Now, A's assignment operator obviously knows only about the members declared in A, so it will copy only those, leaving the members added in Bunchanged.

发生了什么?好吧,默认情况下,C++ 不会将赋值运算符视为virtual. 因此,该行将a_ref = b1调用 的赋值运算符A,而不是的赋值运算符B。这是因为,对于非虚拟函数,声明的(形式上:静态)类型(即A&)确定调用哪个函数,而不是实际(形式上:动态)类型(即B,因为a_ref引用了 的实例B) . 现在,A的赋值运算符显然只知道 中声明的成员A,因此它只会复制那些成员,而B保持添加的成员不变。

A solution

一个办法

Assigning only to parts of an object usually makes little sense, yet C++, unfortunately, provides no built-in way to forbid this. You can, however, roll your own. The first step is making the assignment operator virtual. This will guarantee that it's always the actualtype's assignment operator which is called, not the declaredtype's. The second step is to use dynamic_castto verify that the assigned object has a compatible type. The third step is to do the actual assignment in a (protected!) member assign(), since B's assign()will probably want to use A's assign()to copy A's, members.

仅分配给对象的一部分通常没有什么意义,但不幸的是,C++ 没有提供任何内置方法来禁止这种情况。但是,您可以自己滚动。第一步是使赋值运算符virtual。这将保证它总是被调用的实际类型的赋值运算符,而不是声明的类型。第二步是用来dynamic_cast验证分配的对象是否具有兼容的类型。第三步是在(受保护的!)成员中进行实际分配assign(),因为B'sassign()可能想要使用A'sassign()来复制A's, 成员。

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
  }
};

Note that, for pure convenience, B's operator=covariantly overrides the return type, since it knowsthat it's returning an instance of B.

请注意,为了纯粹的方便,B'operator=协变地覆盖了返回类型,因为它知道它正在返回 的实例B

回答by Black

If You have a base class Aand a derived class B, then You can do the following.

如果您有一个基类A和一个派生类B,那么您可以执行以下操作。

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

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

Now the method wantAnAneeds a copy of derived. However, the object derivedcannot be copied completely, as the class Bcould invent additional member variables which are not in its base class A.

现在该方法wantAnA需要derived. 但是,对象derived不能完全复制,因为该类B可能会发明其他不在其基类中的成员变量A

Therefore, to call wantAnA, the compiler will "slice off" all additional members of the derived class. The result might be an object you did not want to create, because

因此,要调用wantAnA,编译器将“切掉”派生类的所有其他成员。结果可能是您不想创建的对象,因为

  • it may be incomplete,
  • it behaves like an A-object (all special behaviour of the class Bis lost).
  • 可能不完整,
  • 它的行为就像一个A-object(类的所有特殊行为B都丢失了)。

回答by geh

These are all good answers. I would just like to add an execution example when passing objects by value vs by reference:

这些都是很好的答案。我只想在按值和按引用传递对象时添加一个执行示例:

#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());
}

The output is:

输出是:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

回答by The Archetypal Paul

Third match in google for "C++ slicing" gives me this Wikipedia article http://en.wikipedia.org/wiki/Object_slicingand this (heated, but the first few posts define the problem) : http://bytes.com/forum/thread163565.html

谷歌中“C++ 切片”的第三场比赛给了我这篇维基百科文章http://en.wikipedia.org/wiki/Object_slicing和这个(加热,但前几篇文章定义了问题):http: //bytes.com/论坛/thread163565.html

So it's when you assign an object of a subclass to the super class. The superclass knows nothing of the additional information in the subclass, and hasn't got room to store it, so the additional information gets "sliced off".

因此,当您将子类的对象分配给超类时。超类对子类中的附加信息一无所知,也没有空间存储它,因此附加信息被“切掉”。

If those links don't give enough info for a "good answer" please edit your question to let us know what more you're looking for.

如果这些链接没有为“好的答案”提供足够的信息,请编辑您的问题,让我们知道您还需要什么。

回答by Walter Bright

The slicing problem is serious because it can result in memory corruption, and it is very difficult to guarantee a program does not suffer from it. To design it out of the language, classes that support inheritance should be accessible by reference only (not by value). The D programming language has this property.

切片问题很严重,因为它会导致内存损坏,而且很难保证程序不会受到影响。为了在语言之外设计它,支持继承的类应该只能通过引用(而不是通过值)访问。D 编程语言具有此属性。

Consider class A, and class B derived from A. Memory corruption can happen if the A part has a pointer p, and a B instance that points p to B's additional data. Then, when the additional data gets sliced off, p is pointing to garbage.

考虑类 A 和从 A 派生的类 B。如果 A 部分有一个指针 p 和一个 B 实例将 p 指向 B 的附加数据,则可能发生内存损坏。然后,当附加数据被切掉时,p 指向垃圾。

回答by Kartik Maheshwari

In C++, a derived class object can be assigned to a base class object, but the other way is not possible.

在 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
}

Object slicing happens when a derived class object is assigned to a base class object, additional attributes of a derived class object are sliced off to form the base class object.

对象切片发生在派生类对象被分配给基类对象时,派生类对象的附加属性被切掉以形成基类对象。

回答by ididak

The slicing problem in C++ arises from the value semantics of its objects, which remained mostly due to compatibility with C structs. You need to use explicit reference or pointer syntax to achieve "normal" object behavior found in most other languages that do objects, i.e., objects are always passed around by reference.

C++ 中的切片问题源于其对象的值语义,这主要是由于与 C 结构的兼容性。您需要使用显式引用或指针语法来实现大多数其他处理对象的语言中的“正常”对象行为,即对象始终通过引用传递。

The short answers is that you slice the object by assigning a derived object to a base object by value, i.e. the remaining object is only a part of the derived object. In order to preserve value semantics, slicing is a reasonable behavior and has its relatively rare uses, which doesn't exist in most other languages. Some people consider it a feature of C++, while many considered it one of the quirks/misfeatures of C++.

简短的回答是您通过将派生对象按值分配给基础对象对对象进行切片,即剩余的对象只是派生对象的一部分。为了保留值语义,切片是一种合理的行为,并且具有相对罕见的用途,这在大多数其他语言中不存在。有些人认为它是 C++ 的一个特性,而许多人认为它是 C++ 的怪癖/缺陷之一。

回答by Steve Steiner

So ... Why is losing the derived information bad? ... because the author of the derived class may have changed the representation such that slicing off the extra information changes the value being represented by the object. This can happen if the derived class if used to cache a representation that is more efficient for certain operations, but expensive to transform back to the base representation.

那么......为什么丢失派生信息不好?...因为派生类的作者可能已经改变了表示,这样切掉额外的信息就会改变对象所表示的值。如果派生类用于缓存对某些操作更有效的表示,但转换回基本表示的成本很高,则可能会发生这种情况。

Also thought someone should also mention what you should do to avoid slicing... Get a copy of C++ Coding Standards, 101 rules guidlines, and best practices. Dealing with slicing is #54.

还认为有人还应该提到您应该做什么才能避免切片...获取 C++ 编码标准、101 条规则指南和最佳实践的副本。处理切片是#54。

It suggests a somewhat sophisticated pattern to fully deal with the issue: have a protected copy constructor, a protected pure virtual DoClone, and a public Clone with an assert which will tell you if a (further) derived class failed to implement DoClone correctly. (The Clone method makes a proper deep copy of the polymorphic object.)

它提出了一个稍微复杂的模式来完全处理这个问题:有一个受保护的复制构造函数、一个受保护的纯虚拟 DoClone 和一个带有断言的公共 Clone,它会告诉你一个(进一步的)派生类是否未能正确实现 DoClone。(Clone 方法制作了多态对象的适当深拷贝。)

You can also mark the copy constructor on the base explicit which allows for explicit slicing if it is desired.

您还可以在基本显式上标记复制构造函数,以便在需要时进行显式切片。

回答by haberdar

1. THE DEFINITION OF SLICING PROBLEM

1.切片问题的定义

If D is a derived class of the base class B, then you can assign an object of type Derived to a variable (or parameter) of type Base.

如果 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

Although the above assignment is allowed, the value that is assigned to the variable pet loses its breed field. This is called the slicing problem.

虽然上面的赋值是允许的,但赋值给变量 pet 的值丢失了它的品种字段。这称为切片问题

2. HOW TO FIX THE SLICING PROBLEM

2. 如何解决切片问题

To defeat the problem, we use pointers to dynamic variables.

为了解决这个问题,我们使用指向动态变量的指针。

EXAMPLE

例子

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

In this case, none of the data members or member functions of the dynamic variable being pointed to by ptrD (descendant class object) will be lost. In addition, if you need to use functions, the function must be a virtual function.

在这种情况下,ptrD(后代类对象)指向的动态变量的任何数据成员或成员函数都不会丢失。另外,如果需要使用函数,函数必须是虚函数。