下面将从默认构造函数的定义与分类、何时编译器会自动生成、显式声明(= default
/= delete
)、与其他特殊成员函数的关系、性能与可用性注意事项等方面进行详解,并配以示例代码。
一、默认构造函数是什么
-
默认构造函数(default constructor)指的是不带任何参数(或所有参数都有默认值)的构造函数,用于创建对象时不传入显式初始值的场景。
-
形式通常为:
ClassName();
或者
ClassName() = default;
-
默认构造函数用于初始化成员:
- 内置类型(如
int
、double
)如果未在类内提供默认初始值,则其值未定义(除非对象是静态或全局); - 类类型成员则调用其默认构造函数;
- 聚合类型除外(C++11 起可使用聚合初始化)。
- 内置类型(如
二、隐式生成规则
编译器会在没有用户声明任何构造函数时,为类自动生成一个隐式默认构造函数,其行为大致为“为每个直接基类和每个非静态数据成员都调用它们的默认构造”。
struct A { int x; }; // 没有构造函数
A a; // 调用隐式生成的默认构造:a.x 未初始化(非静态存储区则为垃圾)
但在以下情况下,编译器不会自动生成默认构造函数:
- 类中声明了其他任何构造函数(包括带参构造);
- 类中声明了**
= delete
的默认构造函数**; - 某些特殊场景下(如存在不可默认构造的成员)。
三、显式声明 = default
与 = delete
1. = default
-
在类内显式写出
ClassName() = default;
可让编译器生成一个受控的(
constexpr
、noexcept
、trivial
属性由成员决定)默认构造函数。 -
常用于:
- 想保留“隐式构造”的行为,但由于定义了其他构造(或析构、拷贝、移动),编译器不再自动生成,需要手动恢复;
- 明确希望以“可见”的形式声明存在此构造器。
struct B {
B(int) {} // 用户定义构造,编译器不自动生成默认构造
B() = default; // 重新请求生成默认构造
int x = 0;
};
2. = delete
-
写成
ClassName() = delete;
可禁止通过默认构造创建对象,若试图
T obj;
则编译期报错。 -
用于设计上不允许无参构造,强制用户使用带参构造或工厂函数。
struct C {
C() = delete; // 禁用默认构造
explicit C(int v): x(v) {}
int x;
};
C c; // ERROR: 调用被 delete 的构造函数
C c2(5); // OK
四、与其他特殊成员函数的交互
-
若用户声明了任何构造函数,编译器不再自动生成默认构造。
-
= default
可放在类内(隐式内联)或类外(需加上inline
/constexpr
视情况而定)。 -
移动构造、拷贝构造、析构函数 的声明与生成规则相似,定义某些后会抑制其他隐式生成。
- 例如:若显式声明了析构函数,则可能影响移动构造/赋值的隐式生成。
-
聚合类型(聚合)从 C++17 开始可仍进行聚合初始化,即使定义了用户声明的默认构造。
五、何时调用与何时省略
-
直接初始化(如
T obj;
、T obj{};
)会调用默认构造。 -
聚合初始化(如
T obj = {};
)则对聚合类型直接逐成员初始化,不调用用户定义构造。 -
new T;
与new T();
行为不同:new T;
对内置成员不初始化;new T();
对内置成员值初始化(设为 0 或调用默认构造)。
六、性能与可用性注意事项
-
非静态内置成员未初始化
struct S { int a; double b; }; S s; // s.a 和 s.b 未定义 → UB S s2{}; // 值初始化,s2.a=0, s2.b=0.0
- 建议对 POD/内置成员使用类内默认初始化或在构造中显式赋值,或使用
{}
初始化。
- 建议对 POD/内置成员使用类内默认初始化或在构造中显式赋值,或使用
-
constexpr
默认构造- C++11 起可声明
constexpr ClassName() = default;
,令对象可在编译期创建。 - 仅在所有成员默认构造也为
constexpr
时可行。
- C++11 起可声明
-
noexcept
与异常安全-
隐式生成的构造函数对此类成员的
noexcept
属性与成员默认构造函数一致。 -
若希望保证
noexcept
,可显式写:ClassName() noexcept = default;
-
-
规则五/三/零
- 三:如果自定义了析构、拷贝构造或拷贝赋值,考虑自定义其他两项。
- 五:加入移动构造、移动赋值。
- 零:若无特殊资源管理需求,尽量都用
= default
或编译器生成,保持“零”自定义。
七、综合示例
#include <iostream>
#include <vector>
struct POD {
int a; // 内置类型
double b{3.14}; // 类内默认初始化
// 隐式生成默认构造:a 未初始化,b=3.14
};
struct Foo {
Foo() = default; // 请求生成默认构造
Foo(int v): x(v) {} // 带参构造抑制隐式默认构造
~Foo() noexcept = default; // 默认析构,保证 noexcept
Foo(const Foo&) = default; // 显式拷贝
Foo(Foo&&) = default; // 显式移动
Foo& operator=(const Foo&) = default;
Foo& operator=(Foo&&) = default;
int x = 0;
std::vector<int> data; // data 默认构造为空 vector
};
struct Bar {
Bar() = delete; // 禁用默认构造
explicit Bar(int v): y(v) {}
int y;
};
int main() {
POD p1; // p1.a 未定义,p1.b=3.14
POD p2{}; // 值初始化,p2.a=0, p2.b=3.14
Foo f1; // OK:调用 Foo() = default → x=0, data.empty()
Foo f2(10); // 调用带参构造 → x=10
Foo f3 = f2; // 拷贝构造
Foo f4 = std::move(f2); // 移动构造
// Bar b; // ERROR:Bar() 被 delete
Bar b2(5); // OK
Foo* fp1 = new Foo; // 隐式默认构造 → x=0
Foo* fp2 = new Foo(); // 同上,值初始化没有差别
return 0;
}
八、小结
- 自动生成:只有在未声明任何构造函数时,编译器才隐式生成默认构造。
- 显式控制:使用
= default
恢复或声明默认构造,用= delete
禁用。 - 初始化语义:
T obj;
与T obj{};
、new T;
与new T();
在初始化内置成员时有差异。 - 规则五/三/零:结合特殊成员函数设计,保证类型的正确管理与异常安全。