如果我没有显式地初始化C++类成员,它是如何初始化的?

假设我有一个类,其私有成员ptrnamepnamernamecrnameage。如果我不自己初始化它们,会发生什么?这里有一个例子:

class Example {
private:
int *ptr;
string name;
string *pname;
string &rname;
const string &crname;
int age;


public:
Example() {}
};

然后我做:

int main() {
Example ex;
}

如何在EX中初始化成员?指针会发生什么?stringint是否使用默认构造函数string()int()进行0初始化?参考成员呢?还有,常量引用呢?

我想学习它,这样我就可以写更好的(无错误)程序。任何反馈都会有帮助!

133470 次浏览

如果示例类是在堆栈上实例化的,则未初始化的标量成员的内容是随机的和未定义的。

对于全局实例,未初始化的标量成员将被清零。

对于本身是类实例的成员,将调用它们的默认构造函数,因此您的String对象将被初始化。

  • int *ptr;//未初始化的指针(如果是全局的,则为零)
  • string name;//调用了构造函数,用空字符串进行了初始化
  • string *pname;//未初始化的指针(如果是全局的,则为零)
  • string &rname;//如果初始化失败,则编译错误
  • const string &crname;//如果初始化失败,则编译错误
  • int age;//标量值,未初始化且随机(如果是全局的,则归零)

未初始化的非静态成员将包含随机数据。实际上,它们只具有分配给它们的内存位置的值。

当然,对于对象参数(如string),对象的构造函数可以执行默认初始化。

在您的示例中:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value

具有构造函数的成员将调用其默认构造函数进行初始化。

您不能依赖其他类型的内容。

代替显式初始化,类中成员的初始化与函数中局部变量的初始化相同。

对于对象,将调用其默认构造函数。例如,对于std::string,默认构造函数将其设置为空字符串。如果对象的类没有默认构造函数,则如果不显式初始化它,则会出现编译错误。

对于原始类型(指针、整数等),它们是初始化ABC__1的__--它们包含先前在该内存位置发生的任意垃圾。

对于参考文献(例如,std::string&),不对它们进行初始化是ABC__2的__,您的编译器将抱怨并拒绝编译这样的代码。必须始终初始化引用。

因此,在您的特定情况下,如果它们没有显式初始化:

    int *ptr;  // Contains junk
string name;  // Empty string
string *pname;  // Contains junk
string &rname;  // Compile error
const string &crname;  // Compile error
int age;  // Contains junk

如果它在堆栈上,则没有自己的构造函数的未初始化成员的内容将是随机的和未定义的。即使它是全球性的,依赖它们被归零也是一个坏主意。无论它是否在堆栈上,如果一个成员有自己的构造函数,就会被调用来初始化它。

因此,如果您有字符串*pname,指针将包含随机垃圾。但对于String Name,将调用String的默认构造函数,并给出一个空字符串。对于您的引用类型变量,我不确定,但它可能是对某个随机内存块的引用。

首先,让我解释一下什么是mem-initializer-listmem-initializer-list是以逗号分隔的MEM初始化器的列表,其中每个MEM初始化器是成员名称,其后是(,其后是表达式列表,其后是)表达式列表是如何构造构件的。例如,在

static const char s_str[] = "bodacydo";
class Example
{
private:
int *ptr;
string name;
string *pname;
string &rname;
const string &crname;
int age;


public:
Example()
: name(s_str, s_str + 8), rname(name), crname(name), age(-4)
{
}
};

用户提供的无参数构造函数的mem-initializer-listname(s_str, s_str + 8), rname(name), crname(name), age(-4)。该mem-initializer-list意味着name成员由name1初始化,rname成员由对name的引用初始化,crname成员由对name的常量引用初始化,并且用-4的值来初始化age成员。

每个构造函数都有自己的mem-initializer-list,并且成员只能按照规定的顺序(基本上是成员在类中声明的顺序)进行初始化。因此,Example的成员只能按以下顺序初始化:ptrnamepnamernamecrnameage

当您不指定成员的MEM初始化器时,C++标准表示:

如果实体是非静态数据成员..对于类类型.,实体是默认初始化的(8.5).否则,实体不会被初始化。

这里,因为name是类类型的非静态数据成员,所以如果在mem-initializer-list中没有指定name的初始值设定项,则它被默认初始化。Example的所有其他成员没有类类型,因此它们不会被初始化。

当标准规定它们未初始化时,这意味着它们可以具有任何的值。因此,因为上面的代码没有初始化pname,所以它可以是任何东西。

请注意,您仍然必须遵循其他规则,例如必须始终初始化引用的规则。不初始化引用是编译器错误。

您还可以在声明数据成员时对其进行初始化:

class another_example{
public:
another_example();
~another_example();
private:
int m_iInteger=10;
double m_dDouble=10.765;
};

我几乎只使用这种形式,尽管我读到一些人认为它是“不好的形式”,也许是因为它是最近才引入的-我想是在C++11中。对我来说,这更合乎逻辑。

新规则的另一个有用方面是如何初始化本身是类的数据成员。例如,假设CDynamicString是封装字符串处理的类。它有一个构造函数,允许您指定其初始值CDynamicString(wchat_t* pstrInitialString)。您可以很好地将该类用作另一个类中的数据成员,例如封装Windows注册表值的类,在这种情况下,它存储一个邮政地址。要“硬编码”写入的注册表项名称,请使用大括号:

class Registry_Entry{
public:
Registry_Entry();
~Registry_Entry();
Commit();//Writes data to registry.
Retrieve();//Reads data from registry;
private:
CDynamicString m_cKeyName{L"Postal Address"};
CDynamicString m_cAddress;
};

请注意,保存实际邮政地址的第二个String类没有初始值设定项,因此在创建时将调用其默认构造函数-可能会自动将其设置为空白字符串。

这取决于类是如何构造的

要回答这个问题,就必须理解C++语言标准中的一个巨大的switch case语句,而这是一个凡人很难获得直觉的语句。

举个简单例子来说明事情有多难:

main.CPP

#include <cassert>


int main() {
struct C { int i; };


// This syntax is called "default initialization"
C a;
// i undefined


// This syntax is called "value initialization"
C b{};
assert(b.i == 0);
}

在默认初始化中,您可以从:开始https://en.cppreference.com/w/cpp/language/default_initialization我们转到“默认初始化的效果是”部分,并开始CASE语句:

  • “如果T是非POD ”:NO(POD的定义本身就是一个巨大的switch语句)
  • “如果T是数组类型”:否
  • “否则,什么也不做”:因此,它留下了一个未定义的值。

然后,如果有人决定对值进行初始化,我们将转到https://en.cppreference.com/w/cpp/language/value_initialization “ The Effects of Value Initialization Are ”并开始case语句:

  • “如果T是没有默认构造函数或具有用户提供或删除的默认构造函数的类类型”:不是这种情况。现在,您将花20分钟时间搜索这些术语:
    • 我们有一个隐式定义的默认构造函数(特别是因为没有定义其他构造函数)。
    • 它不是用户提供的(隐式定义的)
    • 它不会被删除(= delete
  • “如果T是具有既不是用户提供的也不是删除的默认构造函数的类类型”:是

这就是为什么我强烈建议您永远不要依赖“隐式”零初始化。除非有很强的性能原因,否则请显式初始化所有内容,或者在构造函数上(如果您定义了构造函数),或者使用聚合初始化。否则,你会给未来的开发人员带来非常非常大的风险。