一、虚函数的访问控制
虚函数的访问控制(public、protected、private)会影响其在派生类中的重写和调用规则,这是容易混淆的知识点。
1. public 虚函数
基类中 public 的虚函数在派生类中可以被重写为 public 或 protected,但不能重写为 private(在 C++11 前允许,C++11 后被禁止):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Base {
public:
virtual void publicFunc() {
cout << "Base::publicFunc" << endl;
}
};
class Derived : public Base {
public:
// 正确:重写为public
void publicFunc() override {
cout << "Derived::publicFunc" << endl;
}
};
class Derived2 : public Base {
protected:
// 正确:重写为protected
void publicFunc() override {
cout << "Derived2::publicFunc" << endl;
}
};
|
通过基类指针始终可以调用 public 虚函数,无论派生类将其重写为哪种访问级别:
1 2 3 4 5 6 7
| Base* b1 = new Derived();
b1->publicFunc(); // 正确,输出Derived::publicFunc
Base* b2 = new Derived2();
b2->publicFunc(); // 正确,输出Derived2::publicFunc,尽管在Derived2中是protected
|
2. protected 虚函数
基类中 protected 的虚函数在派生类中可以被重写为 public 或 protected,但不能重写为 private:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Base {
protected:
virtual void protectedFunc() {
cout << "Base::protectedFunc" << endl;
}
};
class Derived : public Base {
public:
// 正确:重写为public
void protectedFunc() override {
cout << "Derived::protectedFunc" << endl;
}
};
|
通过基类指针不能直接调用 protected 虚函数,但可以通过基类的 public 成员函数间接调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Base {
protected:
virtual void protectedFunc() {
cout << "Base::protectedFunc" << endl;
}
public:
void callProtectedFunc() {
protectedFunc(); // 通过public函数间接调用protected虚函数
}
};
// 使用示例
Base* b = new Derived();
b->callProtectedFunc(); // 正确,输出Derived::protectedFunc
|
3. private 虚函数
基类中 private 的虚函数在派生类中仍然可以被重写,但派生类无法直接访问基类的 private 虚函数,且通过基类指针也不能直接调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Base {
private:
virtual void privateFunc() {
cout << "Base::privateFunc" << endl;
}
public:
void callPrivateFunc() {
privateFunc(); // 基类内部可以调用
}
};
class Derived : public Base {
public:
// 正确:重写基类的private虚函数
void privateFunc() override {
cout << "Derived::privateFunc" << endl;
}
};
// 使用示例
Base* b = new Derived();
b->callPrivateFunc(); // 正确,输出Derived::privateFunc
// b->privateFunc(); // 错误,无法直接调用private函数
|
这种模式非常有用 ——基类控制接口,派生类提供实现,这在模板方法设计模式中经常使用。
二、特殊函数的虚函数特性
1. 虚析构函数
析构函数可以是虚函数,而且当类作为基类时,强烈建议将析构函数声明为虚函数。这是为了确保删除基类指针时,能正确调用派生类的析构函数,避免内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class Base {
public:
// 虚析构函数
virtual ~Base() {
cout << "Base析构函数" << endl;
}
};
class Derived : public Base {
private:
int* data;
public:
Derived() {
data = new int[10];
}
// 自动成为虚析构函数
~Derived() override {
delete[] data;
cout << "Derived析构函数" << endl;
}
};
// 正确使用
Base* obj = new Derived();
delete obj;
// 输出:
// Derived析构函数
// Base析构函数
|
如果析构函数不是虚函数,删除基类指针时只会调用基类的析构函数,导致派生类的资源无法释放,造成内存泄漏。
2. 构造函数不能是虚函数
与析构函数不同,构造函数不能被声明为虚函数。原因很简单:
- 构造函数的作用是初始化对象,包括设置 vptr。在对象构造完成前,vptr 还未完全初始化,无法进行动态绑定。
- 虚函数的调用需要知道对象的实际类型,而在构造期间,对象的类型是正在被构造的类型,尚未完全成为派生类对象。
在构造函数中调用虚函数时,不会发生动态绑定,只会调用当前类或其直接基类的函数版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class Base {
public:
Base() {
cout << "Base构造函数" << endl;
print(); // 调用Base::print(),不会调用派生类版本
}
virtual void print() {
cout << "Base::print()" << endl;
}
};
class Derived : public Base {
public:
Derived() {
cout << "Derived构造函数" << endl;
}
void print() override {
cout << "Derived::print()" << endl;
}
};
// 输出:
// Base构造函数
// Base::print()
// Derived构造函数
|