Effective C++ 条款39:明智而审慎地使用private继承
Effective C++ 条款39:明智而审慎地使用private继承
核心思想:private继承是一种实现技术而非接口继承,它意味着"根据某物实现出"。与public继承不同,private继承不会建立is-a关系,而是实现细节的复用机制。优先选择组合(composition),仅在需要访问protected成员、重写虚函数或进行空基类优化时使用private继承。
⚠️ 1. private继承的特性与实现
特性对照:
特性 | public继承 | private继承 |
---|---|---|
接口继承 | 是 | 否 |
类型转换 | 派生类可转基类(向上转型) | 仅在派生类内部可转换 |
基类成员访问 | 保持原访问权限 | 所有基类成员变为private |
设计意义 | is-a关系 | implemented-in-terms-of |
代码实现模式:
// private继承示例
class Timer {
public:explicit Timer(int tickFreq);virtual void onTick() const; // 虚函数
};// 私有继承:Widget根据Timer实现
class Widget : private Timer {
private:virtual void onTick() const override; // 重写虚函数
};// 组合实现相同功能
class Widget {
private:class WidgetTimer : public Timer {virtual void onTick() const override;};WidgetTimer timer_;
};
🚨 2. private继承 vs 组合的决策指南
决策矩阵:
场景 | 推荐方案 | 原因 | 注意点 |
---|---|---|---|
复用实现且无需多态 | ✅ 组合 | 更低的耦合,更好的封装 | 优先选择 |
复用实现且需要重写虚函数 | 🔶 private继承 | 必须继承才能重写 | 基类必须有虚函数 |
访问基类protected成员 | 🔶 private继承 | 组合无法访问protected成员 | 考虑基类设计是否合理 |
空基类优化(EBO) | ✅ private继承 | 节省内存 | 仅对空基类有效 |
实现多态但不暴露接口 | 🔶 private继承+重写 | 隐藏基类接口 | 确保基类析构函数非虚 |
错误使用案例:
// 错误:用public继承代替private
class Stack : public std::vector<int> {
public:void push(int value) { push_back(value); }int pop() { int top = back(); pop_back(); return top;}
};// 客户端误用
Stack s;
s.push(42);
s.erase(s.begin()); // 破坏栈的封装性!
组合替代方案:
class SafeStack {
public:void push(int value) { impl_.push_back(value); }int pop() {if (impl_.empty()) throw EmptyStack();int top = impl_.back();impl_.pop_back();return top;}
private:std::vector<int> impl_; // 组合实现
};// 安全使用
SafeStack s;
s.push(42);
// s.erase(...); // 编译错误:接口被封装
⚖️ 3. 最佳实践与适用场景
场景1:空基类优化(EBO)
class EmptyAllocator { /* 无成员变量 */ };// 组合:占用额外空间
class ContainerA {int data;EmptyAllocator alloc; // 通常占1字节
}; // sizeof ≥ 8(int+填充)// private继承:触发EBO
class ContainerB : private EmptyAllocator {int data;
}; // sizeof = 4(编译器优化)
场景2:访问protected成员
class BaseWithProtected {
protected:virtual void setup() = 0; // 派生类必须实现
};// 必须继承才能访问protected成员
class CustomImpl : private BaseWithProtected {
private:void setup() override { /* 定制实现 */ }
};
现代C++增强:
// C++11 final和override
class Observer : private Observable {
private:virtual void update() override final; // 明确重写并禁止进一步重写
};// C++20 concepts约束
template<typename T>
class SortedCollection : private std::vector<T> {static_assert(std::is_arithmetic_v<T>, "Numeric types only");
};
💡 关键设计原则
-
优先选择组合
// 除非必要,否则用组合替代private继承 class Widget { private:Timer timer_; // 组合而非继承 public:void trigger() { timer_.onTick(); } };
-
警惕名称隐藏
class Base { public:void log() const; };class Derived : private Base { public:using Base::log; // 显式暴露基类方法 };
-
防止多态误用
class Base { public:virtual ~Base() = default; // 基类有虚析构函数 };class Derived : private Base { /*...*/ };// 编译错误:private继承阻止向上转型 // Base* pb = new Derived;
空基类优化实战:
template<typename T, typename Alloc = std::allocator<T>> class CustomVector : private Alloc { // EBO优化 private:T* data_;size_t size_, capacity_; public:// 继承分配器方法using Alloc::allocate;using Alloc::deallocate; };// 验证:sizeof(CustomVector<int>) == 3*sizeof(void*)
多态实现隐藏:
class DatabaseInterface { protected:virtual Result query(const std::string&) = 0; };class SecureDatabase : private DatabaseInterface { private:Result query(const std::string& sql) override {validate(sql); // 安全检查return realQuery(sql);} };// 对外暴露安全接口 class DatabaseClient { public:Result safeQuery(const std::string& sql) {return db_.query(sql); // 调用私有继承实现} private:SecureDatabase db_; };
总结:private继承是C++中一种特殊的工具,应当谨慎使用。在大多数情况下,组合是更好的选择。private继承适用于需要重写基类虚函数、访问基类protected成员或进行空基类优化的情况。设计时应明确区分接口继承和实现继承,避免误用。