对象切片是c++标准行为,指派生类对象按值传递给基类参数时仅复制基类部分,丢失派生类成员、vptr及虚函数行为;引用/指针可避免切片并保留多态。

按值传递基类参数会触发对象切片
直接把派生类对象传给一个接受Base(而非Base&或Base*)的函数,编译器会执行拷贝构造,只复制Base部分的数据成员,派生类新增的字段、虚函数表指针(vptr)、重写的虚函数行为全部被丢弃。这不是 bug,是 C++ 标准定义的行为。
- 切片后对象的
sizeof变成基类大小,内存布局和Base完全一致 - 即使原对象有虚函数,切片后的副本调用的仍是
Base版本——因为 vptr 已被覆盖为Base的 vtable 地址 - 如果派生类里有动态分配的资源(比如
std::vector<char></char>缓冲区),切片副本不会持有它,也不会触发派生类析构逻辑
为什么引用/指针能避免切片而值传递不能
关键在对象生命周期和类型绑定时机:Base&或Base*不创建新对象,只是给原有派生类对象起个别名或加个间接层;而按值传递必须构造一个独立的Base实例,C++ 规定这个构造过程只考虑基类子对象。
- 引用/指针访问时走的是运行时动态绑定:查当前对象的 vptr → 找到对应 vtable → 调用正确槽位的函数
- 值传递后,对象静态类型就是
Base,所有成员访问、函数调用都按Base的布局和语义进行,没有“回溯”机制 - 编译器不会报错,甚至可能静默通过——这是最容易被忽略的隐患点
常见误用场景与修复方式
最典型的是容器存值、工厂函数返回值、日志包装器等地方,开发者图方便写void log(Base b),结果传进来的TCPLog对象只剩下一个空壳Base。
- 把参数改成
const Base&:零拷贝、保留多态、语义清晰 - 如果必须返回新对象,用智能指针封装,比如
std::unique_ptr<base>,而不是Base值 - 禁用
Base的拷贝构造(= delete)可强制暴露问题,但要注意这会影响所有合法使用场景 - 不要依赖
dynamic_cast抢救已切片的对象——切片后连 RTTI 信息都丢了,dynamic_cast会失败或返回nullptr
虚析构函数无法挽救切片对象
有人以为只要基类有virtual ~Base(),切片后的对象析构时就能调用派生类析构函数。这是错的:virtual只对指针/引用有效;切片得到的是纯Base对象,其析构行为完全静态绑定,永远只走Base::~Base()。
真正危险的是:你以为资源被释放了,其实没有。比如派生类在构造时new了一块内存,析构时才delete,一旦被切片,那块内存就彻底泄漏——连Base的析构函数都不会知道它的存在。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











