C++构造函数用protected修饰的作用和目的浅谈

class Store
{
public:
virtual ~Store() = default;
virtual void process(const QSharedPointer<Action>& action) = 0;
protected:
Store() = default;
Store(const Store&) = default;
Store(Store&&) = default;
Store& operator=(const Store&) = default;
Store& operator=(Store&&) = default;
};
如上代码所示,Store类的构造函数用protected修饰,构造函数又用public修饰。这样设计的作用和目的到底是什么呢?
要理解这个Store类的构造函数protected+析构函数public设计,需要结合抽象基类(Abstract Base Class, ABC)的设计目标、C++访问控制语义和多态安全性三个维度分析:
一、先明确类的定位:抽象基类
Store类包含纯虚函数virtual void process(...) = 0,因此是抽象基类——它的核心作用是定义接口规范,强制派生类实现process逻辑,本身不能直接实例化(即使构造函数是public,Store s;也会因抽象类而编译失败)。
二、构造函数protected的目的:强化“抽象基类”的语义与约束
构造函数设为protected,本质是限制类的实例化路径,具体作用:
-
语义明确化:
即使抽象类本身不能实例化,
protected构造函数仍向开发者传递强烈的信号——“这个类是用来被继承的,不要尝试直接创建它的对象”。避免误操作(比如新手可能好奇“抽象类能不能实例化?”)。 -
约束派生类构造:
派生类构造时,必须调用基类
Store的构造函数(这个操作在Qt Widget开发中经常遇到)。protected保证了只有派生类能访问基类构造函数,外部代码无法绕过派生类直接构造基类(虽然抽象类本来也做不到,但更严格)。 -
禁止基类拷贝/移动的误用:
类中的拷贝构造、拷贝赋值、移动构造、移动赋值都是
protected default——这意味着:-
外部代码无法直接拷贝/移动
Store对象(符合抽象类的定位); -
派生类可以自由拷贝/移动自己的实例(因为派生类能访问基类的
protected成员),满足具体派生类的需求(比如复制监控状态)。
-
三、析构函数public的目的:保证多态安全与资源释放
析构函数设为public是多态基类的核心要求,否则会导致严重的运行时或编译错误:
-
支持多态销毁:
当用基类指针(如
Store* ptr = new MonitorStore();)指向派生类对象时,delete ptr;需要先调用派生类的析构函数,再调用基类的析构函数。如果基类析构函数是private或protected,delete表达式无法访问基类析构函数,会导致编译错误(或未定义行为,如果强制调用的话)。 -
避免资源泄漏:
若基类析构函数非
public,派生类对象销毁时,基类的资源(比如成员变量占用的内存、句柄)无法释放,导致资源泄漏。public析构函数确保了“无论通过基类还是派生类指针删除对象,都能正确清理所有资源”。 -
符合C++最佳实践:
C++标准明确要求:多态基类的析构函数必须是
public virtual(这里已经满足virtual+public)。这是避免“切片问题”和“销毁不完整”的关键。
四、总结:设计意图的一体两面
|
访问修饰符 |
设计目的 |
|---|---|
|
构造函数 |
1. 强化抽象基类“不可直接实例化”的语义;2. 约束派生类构造路径;3. 控制拷贝/移动的访问权限。 |
|
析构函数 |
1. 支持多态销毁(基类指针删派生类对象);2. 保证资源正确释放;3. 遵循多态基类的最佳实践。 |
补充:为什么不用private构造函数?
如果构造函数是private,派生类也无法访问基类构造函数,导致无法继承(派生类构造时会报错“无法调用基类的私有构造函数”)。因此protected是“允许继承但禁止直接实例化”的完美平衡。
最终结论
这个设计是抽象基类的标准实践:
-
用
protected构造函数确保类只能被继承,不能直接创建对象; -
用
public virtual析构函数确保多态销毁的安全性和资源释放的正确性。
这种设计常见于框架基类(如这里的监控逻辑基类),目的是让派生类专注于实现具体逻辑,同时保证基类的接口规范和多态安全性。

目送一朵云
更多推荐



所有评论(0)