默认初始化

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 

这是在不使用初始化器构造变量时执行的初始化。

语法

T 对象 ; (1)
new T (2)

解释

默认初始化在三种情况下进行:

1) 当不带初始化器而声明具有自动、静态或线程局部存储期的变量时;
2) 当以不带初始化器的 new 表达式创建具有动态存储期的对象时,或当以带有由一个空括号对组成的初始化器的 new 表达式创建对象时 (C++03 前)
3)构造函数初始化器列表中未提及某个基类或非静态数据成员,且调用了该构造函数时。

默认初始化的效果是:

  • 如果 T非 POD (C++11 前)类类型,那么考虑各构造函数并实施针对空实参列表的重载决议。调用所选的构造函数(即默认构造函数之一),以提供新对象的初始值;
  • 如果 T 是数组类型,那么该数组的每个元素都被默认初始化;
  • 否则不进行初始化(见注解)。

const 对象的默认初始化

如果程序调用有 const 限定类型的 T 对象的默认初始化,那么 T 必须是可 const 默认构造 的类类型或它的数组。

如果类类型 T 的默认初始化会调用用户提供(不是从基类继承) (C++11 起)T 构造函数,或满足以下条件,那么 T 可 const 默认构造:

当未使用初始化器时,仅有具有自动存储期的(可能 cv 限定的)非 POD 类类型(或其数组)认为被默认初始化。具有动态存储期的标量和 POD 类型则认为未被初始化(从 C++11 开始,这种情况被归类为默认初始化的一种形式)。

(C++11 前)
  • T 的每个直接非静态数据成员 M 拥有类类型 X(或它的数组),X 可 const 默认构造,且
  • T 没有直接变体成员,且
(C++11 前)
  • T 的每个直接非变体非静态数据成员 M 均拥有默认成员初始化器,或如果 M 拥有类类型 X(或它的数组),X 可 const 默认构造,
  • 如果 T 是至少拥有一个非静态数据成员的联合体,刚好有一个变体成员拥有默认成员初始化器,
  • 如果 T 不是联合体,那么对于每个至少拥有一个非静态数据成员的匿名联合体成员(如果存在),刚好有一个非静态数据成员拥有默认成员初始化器,且
(C++11 起)

T 的每个潜在构造的基类均可 const 默认构造。

不确定值和错误值

在获取到具有自动或动态存储期的对象的存储时,该对象具有不确定值

如果不对对象进行任何初始化,那么该对象具有不确定值,直到该值被替换。

(C++26 前)

在获取到具有自动或动态存储期的对象的存储时,该对象的存储包含的直接具有以下初始值:

  • 如果对象具有动态存储期,或者与首个声明带有 [[indeterminate]] 的变量或函数形参关联,那么这些字节具有不确定值
  • 否则这些字节具有错误值,其中每个值都由程序确定,并且与程序状态无关。

如果不对对象(包括子对象)进行任何初始化,那么此类字节会保留它们的初始值,直到该值被替换。

  • 如果对象的值表示中存在具有不确定值的位,那么该对象也具有不确定值
  • 否则,如果对象的值表示中存在具有错误值的位,那么该对象也具有错误值
(C++26 起)

如果求值产生了不确定值,那么行为未定义

如果求值产生了错误值,那么行为错误

(C++26 起)

特殊情况

以下类型是未初始化友好类型

(C++17 起)
  • unsigned char
  • char,如果它的底层类型是 unsigned char

给定某个不确定值或错误值 (C++26 起) valuevalue未初始化结果值 是

  • 某个不确定值,如果 value 也是不确定值。
  • value,如果 value 是错误值。
(C++26 起)

如果某个求值 eval 产生了具有未初始化友好类型的不确定值或错误值 (C++26 起) value,那么在以下情况下行为具有良好定义:

  • eval 是对以下表达式和操作数的求值:
这种情况下,操作的结果是某个不确定值。
  • eval 是对简单赋值运算符的右操作数的求值,并且 该运算符的左操作数是具有未初始化友好类型的左值。
这种情况下,左操作数指代的对象的值会被替换成 value 的未初始化结果值。
  • eval 是对初始化表达式的求值,并且要初始化的是具有未初始化友好类型的对象。
  • 如果要初始化的对象不具有 std::byte 类型,那么 value 也不能具有 std::byte 类型。
(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 读取是未定义行为 赋予良好定义

参阅