多态依赖vptr和vtable,构造/析构中调用virtual函数不走多态,因动态类型未完全确立;纯虚函数未实现导致链接时报undefined reference;抽象类析构函数必须为virtual,否则delete时派生类析构不执行。

多态不是靠“写个 virtual 就自动生效”的,它依赖编译器在对象内存布局中插入 vptr 指针,并在运行时通过虚函数表(vtable)跳转——这意味着只要对象没正确构造、vptr 没被初始化,或者调用发生在构造/析构中途,多态就会静默失效。
为什么在构造函数里调用 virtual 函数不走多态?
因为此时派生类部分尚未构造,基类的构造函数只看到自己的 vtable,vptr 指向的是基类的虚函数表,哪怕函数是 virtual,也只会调用当前正在构造的那个类的版本。
- 常见错误现象:
Base::Base()中调用foo(),即使Derived重写了foo(),实际执行的仍是Base::foo() - 根本原因:C++ 标准规定,对象的动态类型在构造期间是“逐层提升”的,构造函数执行时,对象的类型就是当前正在执行的那个类的类型
- 规避方式:不要在构造/析构函数中调用
virtual成员函数;如需类似逻辑,改用模板参数或工厂后置初始化
纯虚函数定义后忘记实现,链接时报什么错?
如果一个类含纯虚函数但未被声明为抽象类(即未用 = 0),或虽声明为纯虚但派生类未实现,链接阶段会报 undefined reference to 'VTT for XXX' 或更直接的 undefined reference to 'XXX::func()' —— 这说明编译器生成了虚表条目,但找不到对应函数地址。
- 典型场景:头文件中写了
virtual void draw() = 0;,但 .cpp 文件漏掉Derived::draw()的定义 - 注意:纯虚函数可以有实现(比如
virtual void init() = 0 { /* common setup */ }),但这样写无法阻止实例化,必须保留= 0才能强制抽象 - 调试技巧:用
nm -C your_obj.o | grep vtable查看虚表符号是否完整,缺失项往往对应未实现的纯虚函数
抽象类指针能指向栈对象,但 delete 它为什么可能崩溃?
因为抽象类的析构函数默认不是 virtual,若基类析构函数非虚,delete 抽象类指针时只调用基类析构,派生类资源(如堆内存、文件句柄)不会释放,且可能触发未定义行为(UB)。
- 错误写法:
class Shape { ~Shape(); };→Shape* s = new Circle; delete s;→Circle::~Circle()不会被调用 - 正确做法:抽象基类的析构函数必须声明为
virtual,哪怕它是空的:virtual ~Shape() = default; - 性能影响:虚析构会增加对象大小(一个
vptr)、略微拖慢析构速度,但在绝大多数面向接口的设计中,这是不可省略的成本
虚函数机制本身很轻量,但它的可靠性完全依赖对象生命周期和内存布局的精确控制——一旦在构造、析构、裸指针管理或跨模块导出时松动一环,多态就退化成静态绑定,而这种退化通常没有编译警告,只能靠内存检查工具或严格测试暴露。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











