按值传参导致虚函数调用退化到基类版本,因为派生类对象被复制为基类对象时发生切片,仅保留基类子对象及虚表指针,丢失派生类虚函数信息;而引用或指针传参不拷贝,维持原对象虚表有效性。

为什么按值传参会导致虚函数调用退化到基类版本
因为对象发生了切片(slicing)——派生类对象被复制进基类参数时,只保留了基类子对象部分,虚表指针也被替换成基类的,运行时再也找不到派生类的虚函数实现。
按值传参 vs 按引用/指针传参的关键差异
同一个 Draw() 调用表达式,在不同传参方式下行为完全不同:
- 按值传参:
void Render(Shape s)→ 编译器构造一个临时Shape对象,拷贝基类部分,丢弃派生类成员和虚表信息 - 按引用传参:
void Render(const Shape& s)→ 不拷贝对象,s仍指向原始派生对象,虚表指针有效 - 按指针传参:
void Render(const Shape* s)→ 同样不拷贝,s指向原对象内存,虚调用可正常查表
常见错误现象与调试线索
你可能会看到这些表现,但容易误判为“虚函数没写对”:
- 断点始终停在基类
Draw()里,进不去派生类重写版本 -
typeid(s).name()在函数体内打印出的是Shape而不是Circle - 派生类特有的成员变量(如
radius_)在函数内访问时报未定义行为或垃圾值 - 即使加了
override关键字、函数签名完全一致,依然不生效
真正起作用的只有两种安全方式
多态必须依赖运行时类型信息,而该信息只存在于原始对象的内存布局中。任何拷贝都会破坏它:
- 用
const Shape&:最常用,零开销,保持对象完整性和虚表有效性 - 用
const Shape*:适合需要空值语义或显式生命周期管理的场景 - 绝对避免
Shape(按值)、Shape&&(移动后仍是基类对象)、std::unique_ptr<shape></shape>(如果构造时用了new Shape{...}而非new Circle{...})
切片不是编译警告,也不报错,它静默发生——这是最容易被忽略的多态失效根源。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











