Namespaces
Variants

Default-initialization

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

这是在对象构造时未提供初始化器所执行的初始化操作。

目录

语法

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

说明

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

1) 当具有自动、静态或线程局部 storage duration 的变量在声明时未提供初始化器;
2) 当具有动态存储期的对象通过不带初始化器的 new-expression 创建时;
3) 当基类或非静态数据成员未在 构造函数初始化列表 中被提及且该构造函数被调用时。

默认初始化的效果是:

  • T 是(可能带有 cv 限定符的) 非 POD (C++11 前) 类类型,则考虑其构造函数并对空实参列表执行 重载决议 。被选中的构造函数(即 默认构造函数 之一)将用于为新对象提供初始值;
  • T 是数组类型,则数组的每个元素均进行默认初始化;
  • 否则,不执行任何初始化(参见 备注 )。

const对象的默认初始化

如果程序要求对 const 限定类型 T 的对象进行默认初始化,则 T 必须是 可常量默认构造的 类类型或其数组。

当且仅当对 T 进行默认初始化会调用 T 的用户提供构造函数 (非从基类继承的构造函数) (自 C++11 起) 时,类类型 T 才满足 const-default-constructible 要求

当未使用初始化式时,仅具有自动存储期的(可能带有 cv 限定符的)非 POD 类类型(或其数组)被视为默认初始化。具有动态存储期的标量和 POD 类型被视为未初始化(自 C++11 起,此情况被重新分类为默认初始化的一种形式)。

(until C++11)
  • T 的每个直接非静态数据成员 M 均为类类型 X (或其数组), X 是 const-default-constructible 的,且
  • T 没有直接的 变体成员 ,且
(C++11 前)
  • T 的每个直接非变体非静态数据成员 M 具有 默认成员初始化器 ,或者如果 M 是类类型 X (或其数组), X 是 const-default-constructible 的,
  • 如果 T 是至少具有一个非静态数据成员的联合体,则恰好有一个 变体成员 具有默认成员初始化器,
  • 如果 T 不是联合体,对于每个具有至少一个非静态数据成员(如果有)的匿名联合体成员,恰好有一个非静态数据成员具有默认成员初始化器,且
(C++11 起)

T 的每个 潜在构造 基类都是可常量默认构造的。

未确定值与错误值

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

如果未对对象执行初始化,该对象将保持不确定值直至该值被替换。

(C++26 前)

当获取具有自动或动态存储期的对象存储空间时,构成该对象存储空间的字节具有以下初始值:

  • 若对象具有动态存储期,或是其首次声明标记有 [[ indeterminate ]] 的变量或 函数形参 关联的对象,则字节具有 不确定值
  • 否则,字节具有 错误值 ,该值由实现独立于程序状态确定。

如果未对对象(包括 子对象 )执行初始化,此类字节将保持其初始值直至该值被替换。

  • 值表示 中的任何位具有不确定值,则该对象具有 不确定值
  • 否则,若值表示中的任何位具有错误值,则该对象具有 错误值
(C++26 起)

若求值产生不确定值,则行为是 未定义的

若求值产生错误值,则行为是 错误的

(C++26 起)

特殊情况

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

(自 C++17 起)
  • unsigned char
  • char ,若其底层类型为 unsigned char

给定一个不确定的 或错误的 (自 C++26 起) value ,其 未初始化结果值 为:

  • 一个不确定值,如果 value 同样是一个不确定值。
  • value ,若 value 为错误值。
(自 C++26 起)

若求值 eval 产生未初始化友好类型的不确定 或错误 (自 C++26 起) value ,则在以下情形中行为是明确定义的:

  • eval 是以下表达式和操作数之一的求值过程:
  • 条件表达式的第二或第三操作数。
  • 逗号表达式的右操作数。
  • 整型转换、显式转换或转换为未初始化友好类型的 static_cast 的操作数。
  • 弃值表达式。
在此情况下,该操作的结果是 value 的未初始化结果值。
  • eval 是对 简单赋值运算符 右操作数的求值,该运算符的左操作数是未初始化友好类型的左值。
在此情况下,左操作数所引用对象的值将被 value 的未初始化结果值所替换。
  • eval 是在初始化未初始化友好类型对象时,对初始化表达式的求值过程。
(自 C++17 起)
在此情况下,该对象被初始化为 value 的未初始化结果值。

转换未初始化友好类型的不确定值会产生不确定值。

转换未初始化友好类型的错误值会产生错误值,转换结果是转换后操作数的值。

(since C++26)
// 案例1:动态存储期的未初始化对象
// 所有C++版本:不确定值 + 未定义行为
int f(bool b)
{
    unsigned char* c = new unsigned char;
    unsigned char d = *c; // 正确,“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 函数中的平凡默认初始化与 asm-declaration

示例

#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 T1 t1;      // 错误:具有隐式默认构造函数的常量类
    [[maybe_unused]]
    T1 t1;            // 类类型,调用隐式默认构造函数
    const T2 t2;      // 常量类类型,调用用户提供的默认构造函数
                      // t2.mem执行默认初始化
}

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。

DR 适用版本 发布时行为 正确行为
CWG 178 C++98 不存在值初始化;
空初始化式调用默认初始化
(尽管 new T ( ) 仍会执行零初始化)
空初始化式调用
值初始化
CWG 253 C++98 const对象的默认初始化不能
调用隐式声明的默认构造函数
当所有子对象均被初始化时允许
CWG 616 C++98 任何未初始化对象的左值到右值转换
均属未定义行为
允许 indeterminate unsigned char
CWG 1787 C++98 读取缓存在寄存器中的 indeterminate unsigned char
属于未定义行为
明确定义为合法行为

另请参阅