当前位置: 首页 > news >正文

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");
};

💡 关键设计原则

  1. 优先选择组合

    // 除非必要,否则用组合替代private继承
    class Widget {
    private:Timer timer_; // 组合而非继承
    public:void trigger() { timer_.onTick(); }
    };
    
  2. 警惕名称隐藏

    class Base {
    public:void log() const;
    };class Derived : private Base {
    public:using Base::log; // 显式暴露基类方法
    };
    
  3. 防止多态误用

    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成员或进行空基类优化的情况。设计时应明确区分接口继承和实现继承,避免误用。

http://www.dtcms.com/a/329786.html

相关文章:

  • RabbitMQ:Windows版本安装部署
  • Java研学-RabbitMQ(六)
  • 基于js和html的点名应用
  • B站 韩顺平 笔记 (Day 17)
  • Spring Security 前后端分离场景下的会话并发管理
  • Spring Boot项目调用第三方接口的三种方式比较
  • 【Linux学习|黑马笔记|Day3】root用户、查看权限控制信息、chmod、chown、快捷键、软件安装、systemctl、软连接、日期与时区
  • Go 微服务限流与熔断最佳实践:滑动窗口、令牌桶与自适应阈值
  • NLP学习之Transformer(1)
  • 深度学习(4):数据加载器
  • Redis7学习——Redis的初认识
  • 51c自动驾驶~合集14
  • Docker:快速部署 Temporal 工作流引擎的技术指南
  • 3DM游戏运行库合集离线安装包下载, msvcp140.dll丢失等问题修复
  • 迅雷链接在线解密解析工具系统源码/本地化API/开源
  • 前缀函数的运用
  • Harmony OS 开发入门 第三章
  • Python Day29 CSS样式
  • Protobuf学习(1)—— 初识与安装
  • 代理解决跨域
  • SparseArray ArrayMap
  • Activity和Fragment生命周期
  • Spring进阶(八股篇)
  • 栈和队列详解
  • LeetCode刷题记录----437.路径总和Ⅲ(medium)
  • 学习:JS进阶[10]内置构造函数
  • HunyuanVideo-Avatar:为多个角色制作高保真音频驱动的人体动画
  • C++哈希进阶-位图
  • 计算机网络技术-知识篇(Day.1)
  • java14学习笔记-打包工具 (Incubator)