More Effective C++(条款四:非必要不提供 default constructor)

本文探讨了默认构造函数在类设计中的作用,特别是在初始化基本类型成员变量时的优势与限制。当类包含自定义类型成员时,可能需要自定义构造函数来确保正确初始化。文章还讨论了不提供默认构造函数的影响及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    默认构造函数可以初始化基本类型的成员变量,但是自定义的类型就不能正确初始化了。在这种情况下,需要我们自定义构造函数。但是,如果我们缺少默认的构造函数,会造成后续使用该类的时候会有一些限制。

1. 有些类的数据成员的值是必要的,因而在创建时就应该提供其值,其中的一个强制实现的方法就是不提供默认构造函数。如果不这么做的话,在使用对象的时候就要检验那个数据成员是否被正确赋值。  

 2. 如果为了强制某些数据成员在创建对象时就被赋值而不提供默认构造函数,那么在创建该对象的数组时就会遭遇因为没有默认构造函数而无法创建的情况,主要的解决方法有以下三种:

例如:

class A{ public : A(int IDNumver);...};

在定义A bP[10];会失败,因为无法调用其默认构造函数。

  1)在堆栈中创建数组,并使用显示初始化列表。如:

 A bP[4]={A(1),A(2),A(3),A(4)};

       缺点:比较麻烦,如果有数组大小为1000,那么初始化列表就要有写1000个构造函数,显然不可能;

                   只适用于堆栈数组,堆中的数组没有此语法

  2)使用指针数组(指针不受构造函数的束缚)。如:

 * ptr_A[10];//不用调用构造函数

**ptr_A=new  (A*)[10];//也可

       缺点:如果指针指向的是堆中内存,则要记得delete,将此数组所指的所有对象删除,否则会造成内存泄露(加重了程序员负担);

                    存放指针需要额外空间,还需空间存放类对象

3)使用内存池:使用operator new[] 函数预先申请一块内存,要使用的时候再使用placement new(定位new)依次构造,如:


void* rawMemory=operator new[](10*sizeof(A));

A *a=static_cast<A*>(rawMemory);

for(int cou=0;cou<10;++cou)
    
    new (&a[i]) A(1);

 优点:可以在堆中创建数组而且不需占用额外空间

       缺点:1)在数组内的对象结束生命时需要手动调用其析构函数,最后调用operator delete[]释放内存

                   for(int i=9;i>=0;--i)

                          a[i].~A();

                    operator delete[](rawMemory);

               2)如果采用一般的数组删除语法,结果将不可预期,因为删除一个不是new operator 获得的指针,结果没有定义。

                  如:delete [] a;         //没有定义,因为a并非来自new操作符

                  故只能采用1)的方法

               3)关于new operator和placement new 以及operator new的区别见条款8.

3. 如果该类没有默认构造函数,那么它有可能不适用有些模板,当然这里的“有些模板"主要指程序员自己定义的模板,对于标准库中的容器模板大多采用了2所述的内存池的方法,这也是使用某个类实例化标准库容器的必要条件只是该类能被复制而并不要求该类有默认构造函数的原因。也就是说,如果类的设计者自定义的类模板(准确的说应该是容器模板)使用内存池的方法的话,这一个缺陷可以避免。

4. 总结:

    尽管不提供默认构造函数对于构造数组会造成一定麻烦,但对于如果仅仅为了方便构建数组而提供一个默认构造函数,在使用某个对象时就需要检查相关的数据成员是否被合理赋值,"调用者便必须为测试行为付出时间代价“,而且提供一个无意义的默认构造函数也会影响类的效率(毕竟默认构造函数的出现只是为了方便构建数组,其实什么也不做),所以如果一个default constructor无法提供所有数据成员都被正确初始化,那么不对其进行定义,尽管可能对类的使用带来限制(如上文所提到的数组的构建),但也保证了使用对象时其必要的数据成员已被合理初始化。



<think>根据用户的问题描述,用户在Linux环境下使用C++编译代码时遇到了与atomic相关的编译错误,具体错误信息是:"error: exception specification of explicitly defaulted default constructor does not match the calculated one"。这个错误通常与C++标准版本和编译器对异常规范的处理有关。 ### 错误原因分析 在C++11及更高版本中,`std::atomic`的默认构造函数被声明为`constexpr`,并且默认是`noexcept`(即抛出异常)。然而,在某些情况下,如果编译器计算出的异常规范(exception specification)与显式默认的声明匹配,就会导致此错误。 例如,在C++11标准中,`std::atomic`的默认构造函数被定义为: ```cpp constexpr atomic() noexcept; ``` 但是,如果代码中显式地要求编译器生成默认构造函数(例如,在类内部使用`= default`),并且编译器计算出的异常规范与上述声明一致,就会产生错误。 ### 解决方案 1. **检查C++标准版本**:确保你的编译器支持C++11或更高版本,并在编译时指定相应的标准(例如,使用`-std=c++11`、`-std=c++14`等)。 2. **检查编译器版本**:更新编译器到最新版本,因为旧版本的编译器可能存在对`noexcept`支持完善的问题。 3. **避免显式默认构造函数**:如果错误发生在自定义类中,并且该类包含`std::atomic`成员,那么可能是由于自定义类的默认构造函数使用了`= default`,但编译器推导出的异常规范与`std::atomic`的构造函数一致。此时,可以尝试显式定义默认构造函数并标记为`noexcept`。 例如,将: ```cpp class MyClass { public: MyClass() = default; private: std::atomic<int> value; }; ``` 改为: ```cpp class MyClass { public: MyClass() noexcept = default; // 显式指定noexcept private: std::atomic<int> value; }; ``` 4. **检查头文件包含**:确保包含了正确的头文件`<atomic>`。 5. **检查代码中的其他部分**:有时候错误可能出现在其他代码中,比如使用了非标准库或第三方库,这些库中的类可能与`std::atomic`有类似的异常规范问题。 ### 示例代码 以下是一个简单的示例,展示了如何正确使用`std::atomic`的默认构造函数: ```cpp #include <atomic> int main() { std::atomic<int> a; // 正确:默认构造函数是noexcept的 return 0; } ``` 编译命令:`g++ -std=c++11 test.cpp -o test` ### 注意事项 - 在C++17中,`std::atomic`的默认构造函数被要求为`constexpr`,并且是`noexcept`的,因此使用C++17标准编译通常可以避免此问题。 - 如果使用GCC或Clang编译器,可以尝试更新到最新版本,因为旧版本(如GCC 5.x)可能存在已知问题。 ### 引用说明 上述分析基于C++标准库的规范以及常见编译器行为。具体实现细节可以参考编译器文档或C++标准文档[^1]。 §§相关问题§§ 1. 如何指定C++的编译标准? 2. C++中`noexcept`关键字的用法是什么? 3. 为什么在C++中需要`std::atomic`? 4. 如何解决C++中其他与异常规范相关的编译错误?</think>### C++ atomic 编译错误分析:默认构造函数异常规范匹配 #### 错误原因分析 这个错误通常发生在以下场景: 1. **C++标准版本冲突**:当使用C++11/14标准编译时,`std::atomic`的默认构造函数声明为`constexpr`但显式声明`noexcept`,而编译器推导的异常规范与实际匹配。 2. **编译器实现差异**:旧版本编译器(如GCC 5.x)对`atomic`的默认构造函数实现与标准存在差异。 3. **显式默认构造函数**:在自定义类中显式默认构造函数时(`= default`),若类包含`atomic`成员,可能导致异常规范冲突。 #### 解决方案 ##### 方法1:升级编译器并指定C++17标准 ```bash # 使用GCC 9+或Clang 10+ g++ -std=c++17 your_code.cpp -o output ``` C++17明确将`atomic`默认构造函数标记为`noexcept`,彻底解决该问题[^1]。 ##### 方法2:显式声明noexcept(C++11/14) ```cpp #include <atomic> class MyClass { public: MyClass() noexcept = default; // 显式声明noexcept private: std::atomic<int> counter; }; ``` ##### 方法3:避免使用默认构造函数 ```cpp std::atomic<int> val{0}; // 使用初始化代替默认构造 // 或 std::atomic<int> val = ATOMIC_VAR_INIT(0); // 传统初始化(C++11) ``` #### 错误示例修正 错误代码: ```cpp #include <atomic> struct Container { Container() = default; // 触发错误 std::atomic<bool> flag; }; ``` 修正方案: ```cpp #include <atomic> struct Container { Container() noexcept = default; // 添加noexcept std::atomic<bool> flag{false}; // 显式初始化 }; ``` #### 根本原因说明 在C++11标准中,`atomic`的默认构造函数定义为: ```cpp constexpr atomic() noexcept; // C++17明确声明 ``` 但早期实现中缺少`noexcept`导致: > 编译器计算的异常规范与显式默认声明匹配 #### 验证方法 ```cpp static_assert(noexcept(std::atomic<int>()), "atomic must be noexcept"); ``` 若断言失败则需应用上述修复方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值