默认初始化
这是在不使用初始化器构造变量时执行的初始化。
语法
T 对象 ;
|
(1) | ||||||||
new T
|
(2) | ||||||||
解释
默认初始化在三种情况下进行:
默认初始化的效果是:
- 如果
T
是非 POD (C++11 前)类类型,那么考虑各构造函数并实施针对空实参列表的重载决议。调用所选的构造函数(即默认构造函数之一),以提供新对象的初始值; - 如果
T
是数组类型,那么该数组的每个元素都被默认初始化; - 否则不进行初始化(见注解)。
当未使用初始化器时,仅有具有自动存储期的(可能 cv 限定的)非 POD 类类型(或其数组)认为被默认初始化。具有动态存储期的标量和 POD 类型则认为未被初始化(从 C++11 开始,这种情况被归类为默认初始化的一种形式)。 |
(C++11 前) |
|
(C++11 前) |
(C++11 起) |
T
的每个潜在构造的基类均可 const 默认构造。
不确定值和错误值
在获取到具有自动或动态存储期的对象的存储时,该对象具有不确定值。 如果不对对象进行任何初始化,那么该对象具有不确定值,直到该值被替换。 |
(C++26 前) |
在获取到具有自动或动态存储期的对象的存储时,该对象的存储包含的直接具有以下初始值:
如果不对对象(包括子对象)进行任何初始化,那么此类字节会保留它们的初始值,直到该值被替换。
|
(C++26 起) |
如果求值产生了不确定值,那么行为未定义。
如果求值产生了错误值,那么行为错误。 |
(C++26 起) |
特殊情况
以下类型是未初始化友好类型:
(C++17 起) |
- unsigned char
- char,如果它的底层类型是 unsigned char
给定某个不确定值或错误值 (C++26 起) value,value 的未初始化结果值 是
- 某个不确定值,如果 value 也是不确定值。
|
(C++26 起) |
如果某个求值 eval 产生了具有未初始化友好类型的不确定值或错误值 (C++26 起) value,那么在以下情况下行为具有良好定义:
- eval 是对以下表达式和操作数的求值:
- eval 是对简单赋值运算符的右操作数的求值,并且 该运算符的左操作数是具有未初始化友好类型的左值。
- 这种情况下,左操作数指代的对象的值会被替换成 value 的未初始化结果值。
- eval 是对初始化表达式的求值,并且要初始化的是具有未初始化友好类型的对象。
(C++17 起) |
- 这种情况下,该对象会被初始化为 value 的未初始化结果值。
从具有未初始化友好类型的不确定值转换会得到一个不确定值。
从具有未初始化友好类型的错误值转换会得到一个错误值,转换结果是转换后的操作数的值。 |
(C++26 起) |
// 情况 1:具有动态存储期的未初始化对象 // 所有 C++ 版本:不确定值+未定义行为 int f(bool b) { unsigned char* c = new unsigned char; unsigned char d = *c; // OK, “d” 具有不确定值 int e = d; // 未定义行为 return b ? d : 0; // “b” 是 true 时是未定义行为 } // 情况 2:具有自动存储期的未初始化对象 // C++26 前:不确定值+未定义行为 // C++26 后:错误值+错误行为 int g(bool b) { unsigned char c; // “c” 具有不确定值/错误值 unsigned char d = c; // 没有未定义行为/错误行为, // 但 “d” 具有不确定值/错误值 assert(c == d); // 成立,但两个整数提升都有未定义行为/错误行为 int e = d; // 未定义行为/错误行为 return b ? d : 0; // “b” 是 true 时是未定义/错误行为 } // 与情况 2 相同 void h() { int d1, d2; // “d1” 和 “d2” 都具有不确定值/错误值 int e1 = d1; // 未定义行为/错误行为 int e2 = d1; // 未定义行为/错误行为 assert(e1 == e2); // 成立 assert(e1 == d1); // 成立,未定义行为/错误行为 assert(e2 == d1); // 成立,未定义行为/错误行为 // 没有未定义行为/错误行为, // 但 “d2” 具有不确定值/错误值 std::memcpy(&d2, &d1, sizeof(int)); assert(e1 == d2); // 成立,未定义行为/错误行为 assert(e2 == d2); // 成立,未定义行为/错误行为 }
注解
不能默认初始化引用和 const 标量对象。
功能特性测试宏 | 值 | 标准 | 功能特性 |
---|---|---|---|
__cpp_constexpr |
201907L | (C++20) | constexpr 函数中的平凡默认初始化和汇编声明 |
示例
#include <string> struct T1 { int mem; }; struct T2 { int mem; T2() {} // “mem” 不在初始化器列表中 }; int n; // 静态非类,进行两阶段初始化: // 1) 零初始化将 n 初始化为零 // 2) 默认初始化不做任何事,令 n 保留为零 int main() { [[maybe_unused]] int n; // 非类,值不确定 std::string s; // 类,调用默认构造函数,值是 "" std::string a[2]; // 数组,默认初始化其各元素,值是 {"", ""} // int& r; // 错误:引用 // const int n; // 错误:const 的非类 // const T1 t1; // 错误:const 的带隐式默认构造函数的类 [[maybe_unused]] T1 t1; // 类,调用隐式默认构造函数 const T2 t2; // const 类,调用用户提供的默认构造函数 // t2.mem 被默认初始化 }
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 178 | C++98 | 没有值初始化;空初始化器会调用默认初始化 (尽管 new T() 也会进行零初始化) |
空初始化器会调用值初始化 |
CWG 253 | C++98 | const 对象的默认初始化不能调用隐式声明的默认构造函数 | 所有子对象已初始化时允许 |
CWG 616 | C++98 | 任何未初始化对象的左值到右值的转换都是未定义行为 | 允许值不确定的 unsigned char |
CWG 1787 | C++98 | 从在寄存器缓存的不确定的 unsigned char 读取是未定义行为 | 赋予良好定义 |