Item19–设计 class 犹如设计 type

🏗️ 条款 19:设计 class 犹如设计 type

(Treat class design as type design)

在 C++ 中,当你创建一个 class(类)时,你实际上是在为这个程序设计一个新的类型(Type)。优秀的类设计必须考虑到与内置类型(如 int, double)同样多的细节,甚至更多。

这个条款提醒我们在设计类时,需要像 C++ 语言的设计者一样,仔细思考所有可能影响用户使用的方面。

1. 考虑对象的创建与销毁 (Creation and Destruction)

你需要决定:

  • 如何创建对象? 考虑构造函数(Constructors)和工厂函数(Factory Functions)。它们应该接受哪些参数?(参考条款 4:注意避免在构造函数中抛出异常。)

  • 如何销毁对象? 考虑析构函数(Destructor)。析构函数需要是虚的 (virtual) 吗?(参考条款 7:为多态基类声明虚析构函数。)

2. 考虑对象的初始化与赋值 (Initialization and Assignment)

你需要决定对象被创建后,能否以及如何被赋值。

  • 赋值行为: 用户可以对你的对象进行赋值吗?如果可以,你需要重载赋值操作符(operator=)。

  • 深拷贝 vs. 浅拷贝: 如果你的类包含指针,是需要执行深拷贝(Deep Copy)来复制指针所指的数据,还是只需要执行浅拷贝(Shallow Copy)?

  • 禁止拷贝: 如果你不希望用户拷贝你的对象(例如,std::unique_ptr),你需要显式地声明这些函数为 delete(参考条款 11:禁止拷贝)。

  • 初始化和赋值的区别: 在构造函数中完成初始化,在赋值操作符中完成赋值,两者行为可能不同。(参考条款 10:让赋值操作符返回一个对 *this 的引用。)

3. 考虑对象的值语义 (Value Semantics)

用户对你的对象执行操作时,它的行为应该像一个内置类型那样自然

  • 合法值: 你的类型有哪些合法值?你需要对构造函数和设置函数进行输入验证,防止对象处于不合法状态。(参考条款 18:让接口容易被正确使用。)

  • 默认值: 你的对象是否有默认状态?如果有,你需要提供默认构造函数。

  • 相等性: 两个对象何时视为相等?你需要考虑重载比较操作符(operator==, operator!= 等)。

4. 考虑对象的类型转换 (Type Conversion)

你需要决定你的类型与其他类型之间如何转换:

  • 隐式转换: 允许你的类型隐式转换为其他类型吗?如果允许,你需要提供隐式转换函数(Conversion Functions)或explicit 的单参数构造函数

    • 注意: 隐式转换常是误用的来源,通常应避免,除非它是接口的必要部分(例如,std::string 允许隐式转换为 C 风格的 const char*)。

  • 显式转换: 如果你需要显式转换,请提供转换成员函数友元非成员函数(例如 static_cast)。

5. 考虑对象的内存分配 (Memory Allocation)

你需要决定是否需要自定义内存管理。

  • 自定义 newdelete 如果你的类有特殊的内存需求(如性能优化、内存池),你可能需要重载 operator newoperator delete。(参考条款 50:小心 operator newoperator delete 的替代品。)

  • 数组形式: 是否需要重载数组形式的 operator new[]operator delete[]?(参考条款 16:成对使用 newdelete 时要采用相同的形式。)

6. 考虑对象的生命周期 (Lifetime Management)

你需要决定谁来管理你的对象的生命周期,这通常涉及到资源管理。

  • RAII: 遵循 RAII(Resource Acquisition Is Initialization)原则,将资源封装在对象中,在构造时获取,在析构时释放。(参考条款 13:以对象管理资源。)

  • 智能指针: 在涉及动态内存和资源时,使用智能指针(std::unique_ptr, std::shared_ptr)来保证对象的自动销毁。


总结要点

方面 对应 C++ 特性/条款 思考要点
构造与析构 构造函数、析构函数 如何创建和销毁?析构函数是否为 virtual
初始化与赋值 拷贝/移动构造、operator= 允许拷贝吗?是深拷贝还是浅拷贝?赋值和初始化有什么区别?
值语义 const、比较操作符 哪些值是合法的?如何比较两个对象是否相等?
类型转换 转换函数、explicit 允许与其他类型隐式/显式转换吗?
内存管理 operator new / operator delete 是否需要自定义内存分配策略?
异常安全 RAII 对象如何处理异常?(参考条款 13, 14, 15)

发表评论