关于对象切片Thinking in C++中有这么一段话:

If you upcast to an object instead of a pointer or reference, something will happen that may surprise you: the object is “sliced” until all that remains is the subobject that corresponds to the destination type of your cast.

在C ++编程,将派生类类型(subclass type)的对象复制到基类类型(superclass)的对象时,将发生对象切片:基类副本将没有在派生类中定义的任何成员变量。实际上,这些变量已被“分割”。

更巧妙地,当基类的赋值运算符将派生类类型的对象复制到相同类型的对象时,对象切片同样会发生,在这种情况下,目标对象的某些成员变量将保留其原始值,而不是从源对象复制过来。

这个问题并不是C ++固有的,但在大多数其他面向对象的语言中也不是自然发生的-甚至在C ++的亲戚(例如D,Java和C#)中也不是这样 — 因为对象的复制不是那些语言的基本操作。

相反,这些语言更喜欢通过隐式引用来操纵对象,这样仅复制引用是一项基本操作。

另外,由于C ++中缺少垃圾回收(garbage collection),因此当不清楚单个共享对象的所有权和生存期时,程序将经常复制该对象。例如,将对象插入标准库集合(例如std :: vector)实际上涉及将副本复制并插入到集合中。

Example

example:

#include <iostream>class Base
{protected:int m_value{};public:Base(int value): m_value{ value }{}virtual const char* getName() const { return "Base"; }int getValue() const { return m_value; }
};class Derived: public Base
{public:Derived(int value): Base{ value }{}virtual const char* getName() const { return "Derived"; }
};int main()
{Derived derived{ 5 };std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';Base &ref{ derived };std::cout << "ref is a " << ref.getName() << " and has value " << ref.getValue() << '\n';Base *ptr{ &derived };std::cout << "ptr is a " << ptr->getName() << " and has value " << ptr->getValue() << '\n';return 0;
}

在上面的示例中,&ref*ptr指向派生对象,派生对象具有基类部分和派生部分。由于refptr的类型为Base,因此refptr只能看到derivedBase部分。

但是derivedDerived部分仍然存在,只是根本无法通过refptr看到。但是,通过使用虚函数,我们可以访问函数的最衍生版本。因此,以上程序打印:

但是,如果我们仅将派生对象分配给基类对象,而不是设置派生对象的基类引用或指针,
会发生什么情况呢?

int main()
{Derived derived{ 5 };Base base{ derived }; // what happens here?std::cout << "base is a " << base.getName() << " and has value " << base.getValue() << '\n';return 0;
}

请记住,派生具有基类部分和派生部分。 当我们将派生对象分配给基类对象时,仅复制派生对象的基类部分,而不复制派生部分。在上面的示例中,base接收了派生的Base部分的副本,但没有 Derived 部分。该派生部分已被有效地 “sliced off”。因此,将派生类对象分配给基类对象称为对象切片(或简称切片)。

因为变量base没有派生部分,所以base.getName()解析为Base :: getName()

认真使用,切片可能是良性的。但是,如果使用不当,切片会以多种不同方式导致意外结果。让我们研究其中一些情况。

Slicing and functions

现在,您可能会认为上面的示例有点愚蠢。毕竟,您为什么要像这样分配派生给基础?你可能不会。但是,切片很可能会偶然发生在函数上。

考虑以下功能:

void printName(const Base base) // note: base passed by value, not reference
{std::cout << "I am a " << base.getName() << '\n';
}

这是一个非常简单的函数,带有const基础对象参数,该参数按值传递。如果我们这样调用此函数:

int main()
{Derived d{ 5 };printName(d); // oops, didn't realize this was pass by value on the calling endreturn 0;
}

在编写该程序时,您可能没有注意到base是一个值参数,而不是引用。因此,当调用printName(d),我们可能期望base.getName()调用virtual函数getName()并显示“I am a Derived”,但是它没有发生。相反,派生对象d被切片并且仅将基类部分复制到基类参数中。执行base.getName()时,即使getName()函数已被虚拟化,该类也没有派生部分可解析。因此,该程序将打印:

当然,修改方法是通过将函数参数作为引用而不是按值传递(这也是按引用而不是按值传递类参数是个好主意的另一个原因),可以轻松避免在此处切片。

void printName(const Base &base) // note: base now passed by reference

Slicing vectors

新程序员在切片方面遇到麻烦的另一个领域是尝试使用std :: vector实现多态。考虑以下程序

#include <vector>int main()
{std::vector<Base> v{};  // std :: vector 被声明为 Base类型的向量v.push_back(Base{ 5 }); // add a Base object to our vectorv.push_back(Derived{ 6 }); // add a Derived object to our vector// Print out all of the elements in our vectorfor (const auto& element : v)std::cout << "I am a " << element.getName() << " with value " << element.getValue() << '\n';return 0;
}


与前面的示例相似,因为std :: vector被声明为Base类型的向量,所以当将Derived(6)添加到该向量时,将其切片。

解决这个问题要困难一些。许多新程序员尝试创建对对象的引用的std :: vector,如下所示:

#include <iostream>
#include <vector>int main()
{std::vector<Base*> v{};Base b{ 5 }; // b and d can't be anonymous objectsDerived d{ 6 };v.push_back(&b); // add a Base object to our vectorv.push_back(&d); // add a Derived object to our vector// Print out all of the elements in our vectorfor (const auto* element : v)std::cout << "I am a " << element->getName() << " with value " << element->getValue() << '\n';return 0;
}


哪个有效? 有关于此的一些评论。首先,nullptr现在是一个有效的选项,可能不希望如此。其次,您现在必须处理指针语义,这可能很尴尬。但是从好的方面来说,这也允许动态内存分配,如果您的对象可能超出范围,这将很有用。

The Frankenobject

在上面的示例中,我们看到了由于划分出派生类而导致切片导致错误结果的情况。现在,让我们看一下派生对象仍然存在的另一种危险情况!

int main()
{Derived d1{ 5 };Derived d2{ 6 };Base &b{ d2 };b = d1; // this line is problematicreturn 0;
}

函数的前三行非常简单。创建两个“派生”对象,并将“base”引用设置为第二个。第四行是误入歧途的地方。

如果b是派生的话,由于b指向d2,我们将d1分配给b,您可能会认为结果将是d1将被复制到d2中 。但是b是一个基类对象,默认情况下C ++为类提供的operator =不是virtual的。因此,只有d1的基本部分被复制到d2中。

结果,您将发现d2现在具有d1的基类部分和d2本身的派生部分。在这个特定示例中,这不是问题(因为Derived类没有自己的数据),但是在大多数情况下,您将创建一个Frankenobject(它由多个对象的一部分组成)。

Conclusion

尽管C ++支持通过对象切片将派生对象分配给基础对象,但是一般来说,这很可能会令人头疼,因此通常应避免切片。确保您的函数参数是引用(或指针),并在涉及派生类时尽量避免传递任何值。

参考文献:

  • Wike:Most_vexing_parse
  • learncpp:12-8-object-slicing

All code :

#include <iostream>
#include <vector>class Base
{protected:int m_value{};public:Base(int value): m_value{ value }{}virtual const char* getName() const { return "Base"; }int getValue() const { return m_value; }
};class Derived : public Base
{public:Derived(int value): Base{ value }{}virtual const char* getName() const { return "Derived"; }
};//void printName(const Base base) // note: base passed by value, not reference
void printName(const Base& base)
{std::cout << "I am a " << base.getName() << '\n';
}int main()
{Derived derived{ 5 };std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';Base &ref{ derived };std::cout << "ref is a " << ref.getName() << " and has value " << ref.getValue() << '\n';Base *ptr{ &derived };std::cout << "ptr is a " << ptr->getName() << " and has value " << ptr->getValue() << '\n';Base base{ derived }; // what happens here?std::cout << "base is a " << base.getName() << " and has value " << base.getValue() << '\n';Derived d{ 5 };printName(d); // oops, didn't realize this was pass by value on the calling end//std::vector<Base> v{};//v.push_back(Base{ 5 }); // add a Base object to our vector//v.push_back(Derived{ 6 }); // add a Derived object to our vector// Print out all of the elements in our vector//for (const auto& element : v)//std::cout << "I am a " << element.getName() << " with value " << element.getValue() << '\n';std::vector<Base*> v{};Base b{ 5 }; // b and d can't be anonymous objects//Derived d{ 6 };v.push_back(&b); // add a Base object to our vectorv.push_back(&d); // add a Derived object to our vector// Print out all of the elements in our vectorfor (const auto* element : v)std::cout << "I am a " << element->getName() << " with value " << element->getValue() << '\n';return 0;
}

Object slicing(对象切片)相关推荐

  1. C++ 进阶——object slicing 与虚函数与dynamic_cast

    C++基础--C++风格的类型转换(static_cast.const_cast.dynamic_cast.reinterpret_cast) 只有再做向上转型(upcast)的动作时,才存在对象切割 ...

  2. 对象切片(object slicing)和多态

    在函数传参处理多态性时,如果一个派生类对象在UpCasting时,用的是传值的方式,而不是指针和引用,那么,这个派生类对象在UpCasting以后,将会被slice成基类对象.https://www. ...

  3. 什么是对象切片(Object Slicing)?

    关于对象切片Thinking in C++中有这么一段话 英文原版: If you upcast to an object instead of a pointer or reference, som ...

  4. java实现ListObject转List实体类,java实现Object转对象,java实现Object转实体类

    摘要:在java开发中,我们常常会遇到Object转对象的情况,最近我就遇到了这个问题,现在记录一下,方便日后自己查看复习! 一:查询Object类型的集合对象的方法如下: List topicLis ...

  5. SDS之Object Storage: 对象存储 - 生于时代,长于场景

    [编者Peter Ye按] 十年了,我还是喜欢下面这张图,它表明了存储发展的趋势. 在这漫长的发展中,对象存储将随之茁壮成长.还记得第一次见这张图的时候,是我2008年从EMC刚跳槽到Compelle ...

  6. java object转对象,object如何强转为对象

    如何将一个object 对象转换为他原来的类型生命中有许多你不想做却不能不做的事,这就是责任;生命中有许多你想做却不能做的事,这就是命运. 想法是自定义一个mvc helper 方法生成一个表格接收o ...

  7. python变量和对象,切片列表元祖

    一,窗口退出三种方法: 1.ctrl+z,Enter 2.quit() 3.exit()二,查看当前python版本号:python --version三,python变量和对象:变量命名: 1.组成 ...

  8. python切片的对象_Python 对象切片

    对对象进行切片 s[a:b:c] 的形式对 s 在 a 和 b 之间以 c 为间隔取值. c 的值还可以为负, 负值意味着反向取值. >>> s = 'bicycle' >&g ...

  9. C++/CLI思辨录之Object的对象布局

    C++/CLI相对纯C++来说,支持创建托管引用对象,托管对象由虚拟机来分配内存和管理,程序员可以不再担心内存泄漏的问题.其实,说白了也就是相当于自己创建一个内存池,并且虚拟机实际上也是这样做的. 在 ...

  10. [JavaScript]Object(对象)学习

    创建空对象: var o = new Object();                 o.a = "A";                 o.b = "B" ...

最新文章

  1. 指针的运用与strcpy函数的优化
  2. python科学计算基础教程pdf下载-用Python做科学计算 pdf版
  3. [HDOJ2845]Beans(dp)
  4. EasyUI三级联动下拉框
  5. 【数据结构和算法笔记】:数据结构概述
  6. [转]bootstrapValidator.js 做表单验证
  7. C语言libxml用法,c语言libxml2库的安装和使用.....
  8. 浅谈信号处理三大变换
  9. 富士施乐Fuji Xerox DocuPrint M225 dw 驱动
  10. 用python来更改小伙伴的windows开机密码,不给10块不给开机
  11. 嵌入式Linux misc 设备驱动
  12. 维修计算机小能人,电脑小能人作文「精选」
  13. 学习笔记:图像分割之深度学习场景分割(2015开始)综述之前是手工特征
  14. ubuntu18.04突然关机重启后显卡驱动失效
  15. 3GPP TS 23501-g51 中英文对照 | 4.4.5 Application Triggering Services
  16. win10突然无法显示图片缩略图怎么办
  17. 2021 年最新的个人录制的前后端真正的免费编程学习视频
  18. Android基础入门教程——1.7 界面原型设计
  19. WinFax使用教程(图)- -
  20. 磁盘不见了只剩一个c盘_无损分区后 磁盘分区不见了只剩一个系统分区怎么办?...

热门文章

  1. 我奋斗了18年,不是为了和你一起喝咖啡
  2. iOS 数据归档解档
  3. String类的getBytes()方法
  4. GitHub打不开解决方案
  5. php微信测试号配置代码,微信测试号实现微信分享等功能【转载】
  6. xposed模拟器安装
  7. Web视频上添加文字
  8. mysql免安装迁移_Mysql 免安装配置并迁移数据库
  9. Altium Designer快捷键,布线技巧
  10. unity如何实现图片透视_实战 | 自己实现扫描全能王