---
title: "C++中默认初始化的一些思考"
date: 2026-08-29
category: "C++"
tags: ["C++", "类", "初始化"]
summary: "引言\\n\\n在学习类的时候，我在learncpp上看到了一个说法：对象的初始化方式不同，其成员的初始化结果也可能不同。当时我并没有放在心上，直到自己动手写了一个测试，才发现默认初始化和列表初始化的结果居然真的不一样。\\n\\n在此篇文章中，我将记录下自己对默认初始化的理解，以供思考，如有不足之处欢迎指正。…"
series:
  name: "C++特性及用法"
  order: 0
---

## 引言

在学习类的时候，我在learncpp上看到了一个说法：对象的初始化方式不同，其成员的初始化结果也可能不同。当时我并没有放在心上，直到自己动手写了一个测试，才发现默认初始化和列表初始化的结果居然真的不一样。

在此篇文章中，我将记录下自己对默认初始化的理解，以供思考，如有不足之处欢迎指正。

## 正文

### 用户默认构造与编译器默认构造

要区分这个问题，首先要区分两种"默认构造函数"：一种是我们自己写的空函数体，另一种是编译器自动生成的。

```cpp
class test1 {
private:
    int m_x;
    int m_y{};//类内初始化，默认为0

public:
    test1() {}//用户定义的默认构造，函数体为空
};

class test2 {
private:
    int m_x;
    int m_y{};

public:
    //不带默认构造函数，编译器自动生成
    //使用显式默认构造函数的效果一样
    //test2() = default;
};
```

表面上这两种写法"什么都不做"，实际上编译器的处理完全不同。
用户定义的`test1() {}`是一个货真价实的函数体为空的构造函数，编译器不会替我们补任何初始化逻辑。

### 默认初始化与列表初始化

有了这两类构造函数，就可以做对比实验了。

```cpp
test1 t1;   //默认初始化，m_x未被初始化，m_y{}为0
test1 t2{}; //列表初始化，m_x同样未被初始化，m_y为0

test2 t3;   //默认初始化，m_x未被初始化
test2 t4{}; //列表初始化，编译器默认构造下m_x被零初始化
```

运行后打印`m_x/m_y`的值，`t1`和`t3`的`m_x`是随机值，而`t4`的`m_x`是0。

***对于有编译器生成（或`= default`）的默认构造函数的类型，列表初始化会触发值初始化，进而对成员做零初始化；而用户定义的空构造函数会挡住这条路径，成员保持默认初始化的结果。***

这么看来，`T t;`和`T t{};`并不是写法上的偏好问题，而是真的可能产生不同的运行结果。

> m_y之所以总是0，是因为类内初始化`int m_y{}`在构造函数初始化列表未提及它时兜底生效，这与构造函数是哪一种无关。

### 直接初始化与拷贝初始化

实验里还有一行：

```cpp
test2 t5 = test2(); //拷贝初始化，test2{}也可以
```

用`=`接一个临时对象，外形上是赋值，实际是拷贝初始化，调用的是拷贝构造（现代编译器大多会将其优化掉，但语义上仍是这样）。

我认为可以这么理解：`test2()`先创建了一个右值临时对象，再用它去初始化`t5`，只不过编译器把中间过程省略了。

### 补充要点

既然用户定义的空构造函数会带来"成员不初始化"的风险，实际写代码时我有两个倾向：要么给成员加类内初始化器，要么用`= default`让编译器生成默认构造。

```cpp
class test3 {
private:
    int m_x{};//类内初始化器，无论哪种构造方式都是0

public:
    test3() = default;
};
```

这样一来，`test3 t;`和`test3 t{};`的结果就一致了，算是把不确定性消掉。

## 小结

默认初始化和列表初始化的区别，核心在于编译器是否有机会进行值初始化，而用户定义的空默认构造函数会把这个机会挡掉。写类的时候多一个`{}`，可能就少一个随机值的坑。

初始化这件事，c++把它藏在了构造函数的细节里，理清楚之后再看类的定义会踏实很多。

本文所用的测试代码收录于[Learncpp](https://github.com/ManJin03/Learncpp)，如有不妥之处欢迎指正。
