作为TotW#182最初发表于2020年7月23日
“在做决定的瞬间,你能做的最好的事情是正确的事情,接下来最好的事情是错误的事情,而你能做的最糟糕的事情就是什么都不做。” ——西奥多·罗斯福
C++很容易让变量未初始化,这非常危险,因为几乎对任何未初始化对象的访问都会导致未定义行为。默认初始化是其中一种形式,当变量没有指定初始值时发生,默认初始化并不总是初始化。
简单类型的默认初始化
{
bool bool_one;
bool bool_two = bool_one;
}
许多人可能惊讶地发现上述代码片段会引发未定义行为。在第一个语句中,bool_one被默认初始化,然而(具有讽刺意味的是)这并不能保证实际上初始化了该变量。在此示例中,尽管使用了“默认初始化”,但bool_one仍然未初始化。我们如何知道这一点呢?
为了理解这个现象,首先要澄清默认初始化何时以及何时不会表现出这种行为。在C++中,并非所有类型都能够跳过初始化。两个值得强调的主要类别如下。
1)对于具有默认构造函数的类型,包括大多数类类型,无论何种情况,默认初始化都会调用默认构造函数。例如,std::string str;保证将str初始化为像std::string str{};这样的值初始化方式一样。
2)对于没有构造函数的类型(如bool),默认初始化可能表现出两种不同的行为。A)如果正在初始化的变量是静态的或在命名空间作用域内定义的,则执行所谓的“值初始化”。B)然而,对于非静态的块作用域变量,对于这些类型,默认初始化不进行任何初始化,导致变量未初始化并具有不确定的值。
因此,在上述示例中,bool_one是未初始化的,因为bool没有构造函数,而bool_one是一个非静态的块作用域变量。当bool_two的初始化读取bool_one的值时,会导致结果不确定的行为。
C++中哪些类型缺少构造函数?
C++从C中继承了称为可以负担得起地进行默认构造的类型(俗称为“简单”类型)的类型,它们没有构造函数。这包括基本类型,如int和double,以及只包含没有成员初始化程序的简单字段的结构类型。甚至包括所有原始指针类型,即使它们指向类(如MyClass*)。
换句话说,由于C没有构造函数,因此在C++中使用这些类型时仍保留了默认初始化的行为。
为什么C++允许未初始化对象?
有时为了性能或提供真正没有初始值的占位符,需要将某些对象保留为未初始化状态。由于大多数对未初始化值的访问模式是未定义的,程序分析器还可以利用此信息来查找错误。
潜在简单类型的默认初始化
与前面的代码片段类似,下面的代码也使用了默认初始化:
{
MyType my_variable;
}
能安全读取my_variable的值吗?
要回答这个问题,我们必须更多地了解MyType的实现。所示的调用点没有足够的信息来确定读取my_variable的值是否安全。例如,如果MyType是一个简单的结构类型,只有int字段,没有构造函数和成员初始化程序,那么my_variable将是未初始化的。但是,如果MyType是一个类类型,并且为MyType::MyType()定义了用户自定义实现,则构造函数负责初始化变量,以便立即读取其值是安全操作。
建议:初始化简单对象
在大多数代码中,您可能不希望出现未初始化的对象。很少情况下,出于性能或编码语义的原因,使用未初始化变量。然而,除非在这些特殊情况之一,否则应优先在结构字段和变量中初始化简单对象,如下面的示例所示:
float ComputeValueWithDefault() {
float value = 0.0; // 通过提供默认值来保证初始化。
ComputeValue(&value);
return value;
}
struct MySequence {
// 成员初始化程序保证了初始化。
MyClass* first_element = nullptr;
int element_count = 0;
};
MySequence GetPopulatedMySequence() {
MySequence my_sequence; // 通过成员初始化程序使其安全。
MaybePopulateMySequence(&my_sequence);
return my_sequence;
}
此外,在可行的情况下,避免为简单类型创建类型别名。我们希望在所有情况下都可以安全初始化结构类型和类类型。由于基本类型(整型、指针等)不能保证来自默认初始化的初始化,给这些类型取一个看起来安全的名称可能会使代码更难理解。
下面显示了简单类型的别名示例:
{
using KeyType = float; // C++风格的别名
typedef bool ResultT; // C风格的别名
// [许多行代码...]
// 惊喜!这些变量未初始化!
KeyType some_key;
ResultT some_result;
}
其他信息
-
CppReference:默认初始化
- 本周小贴士#88:初始化:=、()和{}
- 本周小贴士#146:默认初始化与值初始化