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

More Effective C++ 条款04:非必要不提供默认构造函数

More Effective C++ 条款04:非必要不提供默认构造函数


核心思想默认构造函数并非总是必要的,在某些情况下,强制要求对象在构造时提供必要参数可以创造更安全、更健壮的接口设计,避免对象处于无效状态。

🚀 1. 默认构造函数的利弊分析

1.1 默认构造函数的优势

  • 便于创建数组和标准库容器
  • 简化某些模板代码
  • 支持某些序列化框架

1.2 默认构造函数的劣势

  • 可能允许创建处于无效状态的对象
  • 掩盖了对象的必要初始化要求
  • 增加了接口的模糊性

1.3 问题代码示例

// ❌ 有问题设计:提供默认构造函数但对象可能无效
class Employee {
public:Employee();  // 默认构造Employee(int id, const std::string& name);void setID(int id);void setName(const std::string& name);bool isValid() const;  // 需要检查对象是否有效private:int id_;std::string name_;
};// 使用时的风险
Employee emp;  // 创建了无效对象
// 必须记得调用setter方法,否则对象无效

📦 2. 何时避免默认构造函数

2.1 必需参数缺失时对象无意义的情况

// ✅ 更好设计:强制提供必要参数
class NetworkConnection {
public:// 没有默认构造函数!NetworkConnection(const std::string& address, int port);~NetworkConnection();void sendData(const void* data, size_t size);void receiveData(void* buffer, size_t size);private:std::string address_;int port_;// 连接状态等必需信息
};// 使用:必须提供有效参数
NetworkConnection conn("192.168.1.1", 8080);  // ✅ 有效对象
// NetworkConnection badConn;  // ❌ 编译错误:没有默认构造函数

2.2 不同构造场景的解决方案对比

场景问题解决方案
数组创建Employee employees[10]; 需要默认构造使用指针数组或std::vector
标准库容器vector<Employee> 需要默认构造使用emplace或reserve+push_back
模板代码某些模板需要默认构造使用requires约束或static_assert

⚖️ 3. 解决方案与替代方案

3.1 处理必须使用默认构造的场景

// 方案1:使用指针数组替代对象数组
class Employee {
public:Employee(int id, const std::string& name); // 没有默认构造private:int id_;std::string name_;
};// 创建数组的替代方案
void createEmployeeArray() {// ❌ 不能这样:Employee employees[5];// ✅ 替代方案1:使用指针Employee* employees[5] = {nullptr};employees[0] = new Employee(1, "Alice");// ... 记得手动删除// ✅ 替代方案2:使用vector和emplacestd::vector<Employee> employees;employees.reserve(5);  // 预分配空间employees.emplace_back(1, "Alice");  // 原地构造employees.emplace_back(2, "Bob");// ✅ 替代方案3:使用optional包装std::array<std::optional<Employee>, 5> employeeArray;employeeArray[0] = Employee(1, "Alice");
}

3.2 设计模式应用

// 方案2:使用工厂模式
class EmployeeFactory {
public:static std::unique_ptr<Employee> create(int id, const std::string& name) {return std::make_unique<Employee>(id, name);}// 如果需要"空"对象,提供明确的无效状态static std::unique_ptr<Employee> createInvalid() {// 返回明确标记为无效的对象return std::make_unique<Employee>(-1, "INVALID");}
};// 方案3:使用建造者模式
class EmployeeBuilder {
public:EmployeeBuilder& setId(int id) { id_ = id; return *this; }EmployeeBuilder& setName(const std::string& name) { name_ = name; return *this; }Employee build() const {if (id_ < 0 || name_.empty()) {throw std::invalid_argument("Missing required fields");}return Employee(id_, name_);}private:int id_ = -1;std::string name_;
};// 使用建造者模式
Employee emp = EmployeeBuilder().setId(123).setName("Alice").build();

💡 关键实践原则

  1. 优先考虑对象有效性
    确保对象在构造后立即处于有效状态:

    // ✅ 好设计:构造即有效
    class Date {
    public:Date(int year, int month, int day);  // 验证参数有效性// 没有默认构造函数 - 日期不能"空"
    private:int year_, month_, day_;
    };// ❌ 坏设计:允许无效状态
    class BadDate {
    public:BadDate();  // 创建无效日期// 需要额外方法设置值,期间对象无效
    };
    
  2. 明确表达设计意图
    通过构造函数设计传达业务规则:

    // 业务规则:每个银行账户必须有关联客户
    class BankAccount {
    public:// 强制要求客户信息,避免"无主"账户BankAccount(const Customer& owner, double initialDeposit = 0.0);// 没有默认构造函数!
    };
    
  3. 提供清晰的错误信息
    当缺少必需参数时,在编译期就发现问题:

    // 编译错误比运行时错误更容易发现和修复
    // BankAccount account;  // ❌ 编译错误:没有默认构造函数
    BankAccount account(customer, 100.0);  // ✅ 明确且安全
    

现代C++增强

// 使用Concept约束模板要求
template<typename T>
concept DefaultConstructible = requires {T::T();  // 要求默认构造函数
};// 对于需要默认构造的模板,明确要求
template<DefaultConstructible T>
class Container {// 只能使用有默认构造的类型
};// 使用std::optional处理可能缺失的值
#include <optional>class Configuration {
public:Configuration(const std::string& configPath);  // 必需参数// 但有时可能需要延迟初始化static std::optional<Configuration> fromFile(const std::string& path) {if (/* 文件存在 */) {return Configuration(path);}return std::nullopt;  // 明确表示缺失}
};// 使用std::variant表示多种状态
#include <variant>struct Uninitialized {};
struct Initialized { /* 数据成员 */ };class StatefulObject {
public:StatefulObject() : state_(Uninitialized{}) {}void initialize(const RequiredParams& params) {state_ = Initialized{params};  // 转移到初始化状态}private:std::variant<Uninitialized, Initialized> state_;
};

代码审查要点

  1. 检查每个默认构造函数,确认其创建的对象的有效性
  2. 验证是否有类在缺少必需信息时仍提供了默认构造
  3. 确认数组和容器使用场景都有适当的替代方案
  4. 确保业务规则在构造函数设计中得到正确体现

总结
默认构造函数并非总是必要的设计选择。在许多情况下,避免提供默认构造函数可以创建更安全、更明确的接口,强制使用者在对象构造时提供所有必要信息,从而确保对象始终处于有效状态。虽然这会增加某些使用场景的复杂性(如数组创建、标准库容器使用),但通过智能指针、工厂模式、建造者模式以及现代C++特性如std::optional和std::variant,可以优雅地解决这些问题。在设计类时,应该优先考虑对象的有效性和接口的明确性,而不是为了便利性而提供可能创建无效对象的默认构造函数。

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

相关文章:

  • c++string
  • 【计算机网络 | 第8篇】编码与调制
  • 青少年机器人技术(二级)等级考试试卷-实操题(2024年9月)
  • 笔试——Day47
  • 张老师---个人师资介绍
  • python学习DAY49打卡
  • 智慧矿山误报率↓83%!陌讯多模态融合算法在矿用设备监控的落地优化
  • 鸿蒙中CPU活动分析:CPU分析
  • 周末总结(2024/08/23)
  • 数组拆分求最大不重复数和(动态规划解法)
  • Linux内核进程管理子系统有什么第三十三回 —— 进程主结构详解(29)
  • java猜数字游戏(赌城主题版)
  • 注意力机制:捕获长距离依赖关系的革命性技术
  • mysqlbinlog解析命令
  • 订单号老是撞车?我写了个通用 PHP ID 生成器
  • linux添加新硬盘挂载分区和数据迁移
  • 云计算之云主机Linux是什么?有何配置?如何选?
  • Agent原理、构建模式(附视频链接)
  • Python打卡Day50 预训练模型+CBAM模块
  • 【Camera驱动】GMS测试项中Camera FOV Calibration问题详解
  • ROS机器人运动控制
  • 布偶猫吃什么猫粮比较好?2025猫粮品牌排名
  • 如何创建自己的 Minecraft 世界
  • 8月23号打卡
  • MySql知识梳理之DML语句
  • FL Studio Win版.exe安装教程(直接安装版/详细步骤/附安装包下载)
  • 基于STM32的病房监测系统/环境监测系统/人体健康监测系统
  • 曲面方程的三维可视化:从数学解析到Python实现
  • 分割等和子集
  • React学习(十)