虚函数内调用非虚函数完全合法且常见,调用按静态绑定在编译期确定,仅限于本类或基类中已声明的函数,不会因对象实际类型而动态选择。

虚函数内部调用非虚成员函数完全合法
可以,而且很常见。虚函数本身是普通成员函数,只是多了动态绑定能力,它调用本类或基类的非虚成员函数没有任何语法或语义限制。
关键在于:虚函数的“虚”只影响它自己被外部调用时的绑定方式(运行时查 vtable),不影响它内部调用其他函数的行为。内部调用 non_virtual_func() 仍是静态绑定,编译期就确定目标地址。
- 虚函数中调用本类的非虚函数 → 直接调用本类版本
- 虚函数中调用基类的非虚函数(如
Base::helper())→ 显式指定,不会被派生类同名函数隐藏 - 虚函数中调用派生类新增的非虚函数 → 编译失败:虚函数所在类(比如
Base)根本不知道那个函数存在
为什么 Base::foo() 在虚函数里调用的总是基类版本
因为非虚函数调用走的是静态类型(即当前函数所在的类作用域)。哪怕你在一个 Derived 对象上调用重写的虚函数 Derived::bar(),它内部写 foo(),而 foo() 在 Base 中是非虚的、在 Derived 中又没重定义——那它调用的就是 Base::foo(),不是“根据对象动态选”,而是“按当前作用域规则找”。
这和 this->foo() 的解析过程一致:编译器查的是当前成员函数声明所在的类(Derived 成员函数体内,会先查 Derived 类,再查 Base),但不会跨到运行时对象实际类型去查。
容易踩的坑:误以为非虚函数也会多态
最典型的错误是写出这样的代码:
class Base {
public:
virtual void draw() { render(); } // ✅ 合法,但 render() 是非虚的
void render() { cout
<p>结果是:无论通过 <code>Base*</code> 还是 <code>Derived*</code> 调用 <code>draw()</code>,输出永远是 <code>Base render</code>。因为 <code>Base::draw()</code> 体内调用的 <code>render()</code> 是静态绑定到 <code>Base::render()</code> 的。</p>
- 想让
render()也有多态行为?把它也声明为virtual - 想在
Derived::draw()里调用新版本?必须显式重写draw()并在里面写Derived::render()或this->render()(前提是render()是虚的)
性能与设计意图要分清
虚函数调用一次 vtable 查表,开销极小,但确实存在;而非虚函数调用是直接跳转,更快。不过真正影响设计的不是这点性能,而是语义清晰性:
- 如果某个辅助逻辑在所有派生类中都一样,就做成非虚的,放在虚函数里调用 —— 避免重复代码,也防止误覆盖
- 如果辅助逻辑需要随派生类变化,就必须是虚的,或者用模板、策略模式等替代方案
- 不要为了“看起来统一”而把所有函数都设为虚 —— 虚函数表膨胀、缓存局部性下降、且破坏了接口契约的明确性
真正容易被忽略的是:虚函数的“虚性”不传染。它像一层薄薄的胶片,只覆盖自己被调用的那一瞬间;它里面写的每行代码,仍然严格遵守 C++ 的静态绑定规则。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











