1. 基本概念
联编(Binding) 指的是将函数调用与函数实现关联起来的过程。
静态联编(Static Binding)
-
也称为早期绑定(Early Binding)
-
在编译期间确定调用哪个函数
-
基于变量的静态类型(声明类型)
-
C++语言中,使用对象名加点"."成员选择运算符,去调用对象虚函数,则被调用的虚函数是在编译和链接时确定。(称为静态联编)。
动态联编(Dynamic Binding)
-
也称为晚期绑定(Late Binding)
-
在运行期间确定调用哪个函数
-
基于对象的实际类型(动态类型)
C++语言中,使用类类型的引用或指针调用虚函数(成员选择符"->"),则程序在运行时选择虚函
数的过程,称为动态联编
2. 静态联编详解
2.1 静态联编的几种情况
cpp
#include <iostream>
using namespace std;
class Base {
public:
void nonVirtualFunc() {
cout << "Base::nonVirtualFunc" << endl;
}
void overloadedFunc(int x) {
cout << "Base::overloadedFunc(int): " << x << endl;
}
void overloadedFunc(double x) {
cout << "Base::overloadedFunc(double): " << x << endl;
}
};
class Derived : public Base {
public:
// 隐藏基类的nonVirtualFunc,不是重写!
void nonVirtualFunc() {
cout << "Derived::nonVirtualFunc" << endl;
}
// 添加新的重载版本
void overloadedFunc(const string& s) {
cout << "Derived::overloadedFunc(string): " << s << endl;
}
};
void testStaticBinding() {
cout << "=== 静态联编演示 ===" << endl;
Derived derived;
Base* basePtr = &derived; // 基类指针指向派生类对象
// 情况1:非虚函数调用 - 静态联编
basePtr->nonVirtualFunc(); // 输出: Base::nonVirtualFunc
derived.nonVirtualFunc(); // 输出: Derived::nonVirtualFunc
// 情况2:函数重载 - 静态联编
derived.overloadedFunc(10); // 输出: Base::overloadedFunc(int): 10
derived.overloadedFunc(3.14); // 输出: Base::overloadedFunc(double): 3.14
derived.overloadedFunc("hello");// 输出: Derived::overloadedFunc(string): hello
// 情况3:通过基类指针只能看到基类的重载
basePtr->overloadedFunc(20); // 输出: Base::overloadedFunc(int): 20
basePtr->overloadedFunc(2.71); // 输出: Base::overloadedFunc(double): 2.71
// basePtr->overloadedFunc("world"); // ❌ 错误:基类没有string版本
}
2.2 静态联编的原理
编译器的处理过程:
cpp
// 源代码
Base* ptr = new Derived();
ptr->nonVirtualFunc();
// 编译器的处理(概念上):
// 1. 查看ptr的声明类型:Base*
// 2. 在Base类中查找nonVirtualFunc
// 3. 生成调用Base::nonVirtualFunc的代码
// 4. 函数地址在编译时就已经确定
汇编级别看静态联编:
assembly
; ptr->nonVirtualFunc() 的编译结果
call Base::nonVirtualFunc ; 直接调用,地址在编译时确定
3. 动态联编详解
3.1 动态联编的实现:虚函数
cpp
class Base {
public:
// 虚函数 - 支持动态联编
virtual void virtualFunc() {
cout << "Base::virtualFunc" << endl;
}
virtual void anotherVirtual() {
cout << "Base::anotherVirtual" << endl;
}
// 虚析构函数 - 重要!
virtual ~Base() {
cout << "Base destructor" << endl;
}
};
class Derived : public Base {
public:
// 重写虚函数
void virtualFunc() override {
cout << "Derived::virtualFunc" << endl;
}
void anotherVirtual() override {
cout << "Derived::anotherVirtual" << endl;
}
~Derived() override {
cout << "Derived destructor" << endl;
}
};
class SecondDerived : public Derived {
public:
void virtualFunc() override {
cout << "SecondDerived::virtualFunc" << endl;
}
};
void testDynamicBinding() {
cout << "\n=== 动态联编演示 ===" << endl;
Base* basePtr1 = new Derived();
Base* basePtr2 = new SecondDerived();
Derived* derivedPtr = new SecondDerived();
// 动态联编:根据实际对象类型调用函数
basePtr1->virtualFunc(); // 输出: Derived::virtualFunc
basePtr2->virtualFunc(); // 输出: SecondDerived::virtualFunc
derivedPtr->virtualFunc(); // 输出: SecondDerived::virtualFunc
// 虚析构函数也是动态联编
delete basePtr1; // 先调用Derived::~Derived,再调用Base::~Base
delete basePtr2; // 先调用SecondDerived::~SecondDerived,再调用Derived::~Derived,最后Base::~Base
delete derivedPtr;
}
3.2 动态联编的原理
编译器的处理过程:
cpp
// 源代码
Base* ptr = new Derived();
ptr->virtualFunc();
// 编译器的处理(概念上):
// 1. 发现virtualFunc是虚函数
// 2. 生成通过vtable间接调用的代码:
// - 通过ptr找到vptr
// - 通过vptr找到vtable
// - 在vtable中找到virtualFunc的地址
// - 调用该地址对应的函数
汇编级别看动态联编:
assembly
; ptr->virtualFunc() 的编译结果
mov rax, qword ptr [ptr] ; 获取对象地址
mov rax, qword ptr [rax] ; 获取vptr(对象的前8字节)
mov rax, qword ptr [rax] ; 获取vtable中第一个函数的地址
call rax ; 间接调用,地址在运行时确定
4. 对比实验:区分两种联编
cpp
class Calculator {
public:
// 静态联编:函数重载
int calculate(int a, int b) {
cout << "Calculator::calculate(int, int)" << endl;
return a + b;
}
double calculate(double a, double b) {
cout << "Calculator::calculate(double, double)" << endl;
return a * b;
}
// 动态联编:虚函数
virtual double compute(int a, int b) {
cout << "Calculator::compute" << endl;
return a * b;
}
};
class ScientificCalculator : public Calculator {
public:
// 隐藏基类的calculate(静态联编)
double calculate(double a, double b) {
cout << "ScientificCalculator::calculate(double, double)" << endl;
return a / b;
}
// 重写虚函数(动态联编)
double compute(int a, int b) override {
cout << "ScientificCalculator::compute" << endl;
return pow(a, b);
}
};
void contrastExperiment() {
cout << "=== 联编方式对比实验 ===" << endl;
ScientificCalculator sciCalc;
Calculator* calcPtr = &sciCalc;
cout << "\n1. 静态联编(非虚函数):" << endl;
sciCalc.calculate(10, 20); // 调用基类版本(静态联编)
sciCalc.calculate(10.0, 20.0); // 调用派生类版本(静态联编)
calcPtr->calculate(5, 3); // 调用基类版本(静态联编)
cout << "\n2. 动态联编(虚函数):" << endl;
sciCalc.compute(2, 3); // 调用派生类版本
calcPtr->compute(2, 3); // 调用派生类版本(动态联编!)
}
5. 联编方式的决定因素
5.1 影响联编方式的因素
cpp
class Test {
public:
void normalMethod() {} // 静态联编
virtual void virtualMethod() {} // 动态联编
};
void analyzeBindingFactors() {
Test obj;
Test* ptr = &obj;
Test& ref = obj;
cout << "=== 联编方式分析 ===" << endl;
// 情况1:通过对象调用 - 总是静态联编
obj.normalMethod(); // 静态联编
obj.virtualMethod(); // 静态联编!(编译器可以确定对象类型)
// 情况2:通过指针调用 - 根据函数类型决定
ptr->normalMethod(); // 静态联编
ptr->virtualMethod(); // 动态联编
// 情况3:通过引用调用 - 根据函数类型决定
ref.normalMethod(); // 静态联编
ref.virtualMethod(); // 动态联编
// 情况4:在构造函数中调用
// 情况5:在析构函数中调用
// (构造函数和析构函数中总是静态联编)
}
5.2 构造函数和析构函数中的联编
cpp
class Base {
public:
Base() {
cout << "Base构造函数中: ";
show(); // 静态联编!在构造期间对象还不是派生类
}
virtual ~Base() {
cout << "Base析构函数中: ";
show(); // 静态联编!在析构期间对象已不是派生类
}
virtual void show() {
cout << "Base::show" << endl;
}
};
class Derived : public Base {
public:
Derived() {
cout << "Derived构造函数中: ";
show(); // 静态联编
}
~Derived() override {
cout << "Derived析构函数中: ";
show(); // 静态联编
}
void show() override {
cout << "Derived::show" << endl;
}
};
void testConstructorBinding() {
cout << "\n=== 构造/析构函数中的联编 ===" << endl;
Derived derived;
cout << "--- 对象创建完成 ---" << endl;
// 正常使用时的动态联编
Base* ptr = &derived;
cout << "正常使用: ";
ptr->show(); // 动态联编:Derived::show
}
输出结果:
text
Base构造函数中: Base::show
Derived构造函数中: Derived::show
--- 对象创建完成 ---
正常使用: Derived::show
Derived析构函数中: Derived::show
Base析构函数中: Base::show
6. 性能对比分析
6.1 性能测试代码
cpp
#include <chrono>
using namespace std::chrono;
class PerformanceTest {
public:
void staticMethod() {
// 一些简单工作
volatile int result = 0;
for (int i = 0; i < 100; ++i) {
result += i;
}
}
virtual void virtualMethod() {
// 同样的工作
volatile int result = 0;
for (int i = 0; i < 100; ++i) {
result += i;
}
}
};
void performanceComparison() {
const int iterations = 100000000; // 1亿次调用
PerformanceTest obj;
PerformanceTest* ptr = &obj;
cout << "=== 性能对比 ===" << endl;
// 测试静态联编性能
auto start1 = high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
obj.staticMethod(); // 静态联编
}
auto end1 = high_resolution_clock::now();
// 测试动态联编性能
auto start2 = high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
ptr->virtualMethod(); // 动态联编
}
auto end2 = high_resolution_clock::now();
auto static_time = duration_cast<milliseconds>(end1 - start1);
auto virtual_time = duration_cast<milliseconds>(end2 - start2);
cout << "静态联编: " << static_time.count() << "ms" << endl;
cout << "动态联编: " << virtual_time.count() << "ms" << endl;
cout << "性能差异: " << (virtual_time.count() - static_time.count()) << "ms" << endl;
}
7. 实际应用建议
7.1 选择联编方式的准则
cpp
// 适合静态联编的情况:
class MathUtils {
public:
// 工具函数,行为固定
static double sqrt(double x) { return std::sqrt(x); }
static double sin(double x) { return std::sin(x); }
// 重载函数,编译时就能确定
int process(int x) { return x * 2; }
double process(double x) { return x * 1.5; }
};
// 适合动态联编的情况:
class Document {
public:
virtual void save() = 0; // 不同文档格式保存方式不同
virtual void print() = 0; // 打印行为可能不同
virtual void close() = 0; // 关闭前的清理工作不同
virtual ~Document() = default;
};
class PdfDocument : public Document {
void save() override { /* PDF保存逻辑 */ }
void print() override { /* PDF打印逻辑 */ }
void close() override { /* PDF关闭逻辑 */ }
};
class WordDocument : public Document {
void save() override { /* Word保存逻辑 */ }
void print() override { /* Word打印逻辑 */ }
void close() override { /* Word关闭逻辑 */ }
};
7.2 设计原则
-
默认使用静态联编:性能更好
-
需要多态时使用动态联编:当行为需要根据实际对象类型变化时
-
基类析构函数应该是虚函数:确保正确清理资源
-
避免在构造/析构函数中调用虚函数:此时不是多态行为
8. 总结
静态联编 vs 动态联编
| 特性 | 静态联编 | 动态联编 |
|---|---|---|
| 确定时间 | 编译期间 | 运行期间 |
| 决定依据 | 变量/参数的静态类型 | 对象的实际类型 |
| 实现机制 | 直接函数调用 | 通过vtable间接调用 |
| 性能 | 高效 | 有额外开销 |
| 灵活性 | 较低 | 很高 |
| 适用场景 | 函数重载、模板、非虚函数 | 虚函数、多态 |
关键要点:
-
静态联编是默认行为,适用于大多数情况
-
动态联编通过虚函数实现,支持运行时多态
-
联编方式在调用时确定,不是声明时
-
理解两种联编的区别有助于写出高效、正确的代码