基类指针能指向派生类对象仅当为public继承,private/protected继承会因破坏“is-a”关系导致编译错误;虚函数调用实现动态绑定,普通函数仍静态绑定到基类版本。

基类指针能直接指向派生类对象,但必须满足 public 继承
可以,但前提是派生关系是 public。如果用了 private 或 protected 继承,编译器会报错:cannot convert from 'Derived*' to 'Base*'。这是因为非 public 继承切断了“is-a”关系,编译器不认为派生类对象天然可被基类指针安全持有。
常见错误:定义 class Derived : private Base 后尝试写 Base* p = new Derived; —— 直接编译失败,不是运行时问题,也跟虚函数无关。
- 只允许
public继承下做这种转换(隐式向上转型) -
static_cast不能绕过这个限制;强行加会触发编译错误,不是警告 - 多重继承时,若多个基类同名,需显式指定路径,否则编译器无法确定转换目标
调用虚函数才体现多态,普通函数调用绑定在编译期
基类指针指向派生类对象后,调用 virtual 函数会执行派生类版本,这是动态绑定;但普通成员函数(非 virtual)仍调用基类定义的版本,哪怕派生类重写了它。
示例:
立即学习“C++免费学习笔记(深入)”;
struct Base {
virtual void f() { cout << "Base::f\n"; }
void g() { cout << "Base::g\n"; }
};
struct Derived : public Base {
void f() override { cout << "Derived::f\n"; }
void g() { cout << "Derived::g\n"; }
};
<p>Base* p = new Derived;
p->f(); // 输出 "Derived::f"
p->g(); // 输出 "Base::g" —— 不是重写,是隐藏
- 只有声明为
virtual的函数才能通过基类指针触发多态行为 - 派生类中同名非虚函数不会覆盖基类函数,而是造成名称隐藏(name hiding)
- 想让非虚函数也“多态”,必须手动在派生类中显式调用,或改用模板/策略模式
delete 基类指针前,析构函数必须是 virtual
如果基类析构函数不是 virtual,用 Base* 指向 Derived 对象并 delete,只会调用 Base::~Base(),派生类部分的资源(如堆内存、文件句柄)不会释放,导致内存泄漏或未定义行为。
典型现象:程序看似正常运行,但 valgrind 报告“still reachable”块,或 RAII 资源没被 clean up。
- 只要存在通过基类指针管理派生类对象的场景,
~Base()就必须声明为virtual - 即使基类没有数据成员、不写析构函数,也要加
virtual ~Base() = default; - 纯虚析构函数也得提供定义:
virtual ~Base() = 0;+Base::~Base() {}
static_cast 和 dynamic_cast 在向下转型时行为不同
从 Base* 回转到 Derived* 属于向下转型(downcast),这时不能靠隐式转换,必须显式 cast。但选 static_cast 还是 dynamic_cast,取决于你是否确定类型安全。
static_cast 不做运行时检查,强制转换 —— 如果指针实际指向的不是 Derived,后续访问派生类成员就是未定义行为;dynamic_cast 在指针场景下失败时返回 nullptr,更安全但有性能开销(需 RTTI 开启)。
- 仅当逻辑上 100% 确定类型时,才用
static_cast<Derived*>(p) - 不确定时,用
dynamic_cast<Derived*>(p)并检查是否为nullptr - 若基类无虚函数,
dynamic_cast编译失败(要求至少一个虚函数以启用 RTTI)
基类指针指向派生类对象本身很简单,真正容易出问题的地方都在后续操作里:虚函数没设、析构函数没虚、向下转型乱 cast、继承方式选错——这些都不是语法错误,但会让程序在特定路径下崩溃或泄漏,且难以复现。


















