Effective C++ 条款19:设计class犹如设计type
核心思想:设计新的class时,应当像语言设计者设计内置类型一样慎重,考虑对象的创建、销毁、初始化、拷贝、类型转换等所有方面。
⚠️ 1. 类设计的关键问题域
对象生命周期管理:
class ResourceHandle {
public:// 构造和析构:资源如何获取?如何释放?ResourceHandle(const std::string& resId);~ResourceHandle(); // 需要释放资源吗?private:Resource* resource_;
};
值语义与行为:
class Rational {
public:// 拷贝操作:允许拷贝吗?浅拷贝还是深拷贝?Rational(const Rational& other);Rational& operator=(const Rational& other);// 类型转换:支持隐式转换吗?operator double() const; // 危险:可能非预期转换
};
🚨 2. 解决方案:系统化设计方法
明确对象创建方式:
class Session {
public:// 静态工厂方法:控制创建逻辑static Session createFromNetwork();static Session createFromFile(const std::string& path);// 禁用拷贝Session(const Session&) = delete;Session& operator=(const Session&) = delete;
private:Session(); // 私有构造
};
安全类型转换接口:
class SafeRational {
public:// 显式转换函数(C++11)explicit operator double() const { return static_cast<double>(numerator)/denominator; }// 转换运算符替代方案double toDouble() const { /* ... */ } // 更安全的显式转换
};
⚖️ 3. 关键设计原则与决策
设计维度 | 关键问题 | 推荐实践 |
---|---|---|
对象创建/销毁 | 构造函数参数?析构函数必要性? | RAII模式管理资源 |
初始化/赋值区别 | 构造函数与赋值操作符行为是否一致? | 确保一致性 |
值传递方式 | pass-by-value是否高效? | 小对象传值,大对象传const引用 |
操作符重载 | 哪些操作符需要重载? | 仅重载符合直觉的操作符 |
类型转换控制 | 是否允许隐式转换? | 使用explicit 禁止非预期转换 |
成员访问权限 | 哪些成员公开?哪些需要保护? | 最小化public接口 |
继承体系设计 | 是否作为基类?虚函数如何设计? | 明确声明final 或override |
模板泛化可能性 | 是否应设计为类模板? | 评估未来需求 |
标准库兼容性 | 是否满足STL容器要求? | 提供必要的类型特征 |
成员函数设计规范:
class Polynomial {
public:// 常量成员函数:不修改对象状态double evaluate(double x) const noexcept;// 异常安全保证void normalize() &; // 仅限左值对象调用// 引用限定符(C++11)void process() &&; // 仅限右值对象调用
};
继承体系设计规范:
// 接口类设计
class Drawable {
public:virtual void draw() const = 0;virtual ~Drawable() = default;// 禁止拷贝(接口类通常不可拷贝)Drawable(const Drawable&) = delete;Drawable& operator=(const Drawable&) = delete;
};// 具体实现类
class Circle final : public Drawable {
public:void draw() const override; // 明确重写// ... // 禁止进一步继承(final)
};
💡 关键原则总结
- 生命周期全周期设计
- 构造/析构:资源获取即初始化(RAII)
- 拷贝控制:明确
=default
/=delete
拷贝操作
- 类型行为一致性
- 操作符重载:行为需符合内置类型预期
- 类型转换:优先使用
explicit
和命名转换函数
- 接口最小化原则
- 成员函数:提供完备但最小的操作集合
- 访问控制:严格限制
private
/protected
- 继承体系明确性
- 基类:声明虚析构函数,明确抽象接口
- 派生类:使用
final
/override
明确意图
危险类设计示例:
class AutoPtr { // 已废弃的auto_ptr问题 public:// 问题1:允许从临时对象构造AutoPtr(AutoPtr& other); // 非const引用// 问题2:转移所有权但不明确AutoPtr& operator=(AutoPtr& other);// 问题3:支持隐式指针转换operator void*() const; // 可能导致误用 };
安全重构方案:
// 解决方案:现代unique_ptr设计理念 template<typename T> class UniquePtr { public:// 明确所有权转移语义UniquePtr(UniquePtr&& other) noexcept; // 移动构造UniquePtr& operator=(UniquePtr&& other) noexcept; // 移动赋值// 禁止拷贝UniquePtr(const UniquePtr&) = delete;UniquePtr& operator=(const UniquePtr&) = delete;// 显式bool转换(安全)explicit operator bool() const noexcept;// 明确资源释放接口void reset() noexcept;T* release() noexcept; };