对象切片不是编译错误但会静默丢失派生类成员和虚函数,函数参数禁用基类值类型、容器禁用基类值存储、基类应禁用拷贝并声明虚析构,返回值也需避免基类对象。

对象切片不是编译错误,也不会报 warning,但它会让派生类的成员变量和虚函数调用悄无声息地失效——你写的 Dog 对象传进函数后,变成一个没名字、不会叫、只会输出 "Animal speaks" 的 Animal。
函数参数别用基类值类型
这是最常见也最危险的切片入口。只要函数签名里是 Base b 这种写法,传入 Derived 就必然触发拷贝构造,只保留 Base 子对象。
- 错误写法:
void draw(Shape s)→s是全新拷贝,Circle::radius和重写的draw()全部丢失 - 正确写法:
void draw(const Shape& s)或void draw(const Shape* s)→ 不复制,直接访问原对象 - 如果函数内部要修改对象,且明确需要独立副本,应显式设计深拷贝逻辑,而不是依赖默认拷贝构造
容器别存 std::vector<base>
把派生类对象 push 进 std::vector<base>,每插入一次就切一次片。最终容器里全是“空壳”基类对象,连虚函数表都指向基类实现。
- 错误写法:
std::vector<animal> zoo; zoo.push_back(Dog());</animal>→Dog的breed字段和bark()行为全丢 - 推荐写法:
std::vector<:unique_ptr>></:unique_ptr>或std::vector<:shared_ptr>></:shared_ptr> - 注意:指针容器本身不管理对象生命周期,用裸指针(
std::vector<animal></animal>)需自行确保对象存活时间长于容器
禁用基类拷贝操作能提前暴露问题
如果基类本就不该被拷贝(比如纯接口类),把它拷贝构造和赋值运算符设为 = delete,编译器会在你写出 Base b = Derived(); 时立刻报错,而不是放任隐患潜伏。
立即学习“C++免费学习笔记(深入)”;
- 加在基类里:
Base(const Base&) = delete;和Base& operator=(const Base&) = delete; - 必须同时声明虚析构函数:
virtual ~Base() = default;,否则通过指针 delete 会 UB - 这个做法不解决运行时多态逻辑,但能强制团队统一使用引用/指针,从源头堵住误用
返回值也容易踩坑:别返回基类对象
函数声明为 Base create(),哪怕内部 return Derived(),也会在返回时发生一次切片——因为返回值类型是 Base,编译器会调用 Base 的移动或拷贝构造。
- 错误写法:
Animal getPet() { return Dog("Golden"); }→ 返回的是切片后的Animal - 正确写法:
std::unique_ptr<animal> getPet() { return std::make_unique<dog>("Golden"); }</dog></animal> - 如果调用方需要栈上对象,说明设计本身可能违背多态意图;优先考虑是否真需要值语义,还是只是没想清楚所有权模型
最隐蔽的切片往往发生在你没意识到自己在“复制”的时候:比如临时对象隐式转换、auto 推导出基类类型、甚至某些模板实例化中自动降级。只要涉及多态,就默认所有交互路径都该走指针或引用——这不是权衡,而是前提。


















