本周小贴士#182:初始化你的整形变量

  • Post author:
  • Post category:其他


作为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;
}



其他信息