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


158

假设我有私人承包商,客人一类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获得0 intialized默认构造函数string()int()?参考成员呢?还有const引用呢?

我还应该知道些什么?

有谁知道涵盖这些情况的教程?也许在一些书中?在大学图书馆,我可以使用很多C ++书籍。

我想学习它,以便编写更好的(无错误)程序。任何反馈将有所帮助!


3
对于书的建议,请参阅stackoverflow.com/questions/388242/...
迈克·西摩

迈克,嗯,我的意思是解释它的那本书的一章。不是整本书!:)
bodacydo 2010年

不过,最好阅读一整本要使用的编程语言的书。而且,如果您已经读过一本并且没有解释这一点,那么它并不是一本好书。
泰勒·麦克亨利

2
斯科特·迈耶斯(Scott Meyers,流行的前专业C ++咨询专家)在《有效C ++》中指出:“规则很复杂-太复杂,以至于不值得记住。...确保所有构造函数都初始化对象中的所有内容。” 因此,在他看来,以(试图)写“无缺陷”的代码最简单的方法就是不要试图记住的规则(事实上他并没有布置它们在书),但要明确初始化一切。但是请注意,即使您在自己的代码中采用了这种方法,您也可能会在没有这样做的人的项目上工作,因此规则可能仍然很有价值。
凯尔·斯特兰德

2
@TylerMcHenry您认为有关C ++的哪些书籍“不错”?我已经阅读了几本有关C ++的书,但没有一本完整地解释了这本书。正如我之前的评论所指出的那样,Scott Meyers明确拒绝提供有效的C ++中的完整规则。我还阅读了Meyers的《Effective Modern C ++》,Dewhurst的C ++常识和Stroustrup的C ++之旅。在我的记忆中,他们都没有解释完整的规则。显然我可以阅读该标准,但是我几乎不会认为这是一本“好书”!:D并且我希望Stroustrup可能会在C ++编程语言对此进行解释。
凯尔·斯特兰德

Answers:


206

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

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

对于原始类型(指针,整数等),它们不会初始化-它们包含先前在该内存位置碰巧的任意垃圾。

对于引用(例如std::string&),不对其进行初始化是非法的,并且您的编译器将抱怨并拒绝编译此类代码。引用必须始终初始化。

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

    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

4
+1。值得注意的是,按照严格的标准定义,原始类型的实例以及各种其他事物(任何存储区域)都被视为对象
stinky472 2010年

7
“如果对象的类没有默认的构造函数,那么如果您未显式初始化它,将是编译错误”,这是错误的!如果一个类没有默认构造函数,则为它提供一个默认的默认构造函数,该构造函数为空。
巫师2010年

19
@wiz我认为他的字面意思是“如果对象没有默认构造函数”,也就是没有生成任何构造函数,如果该类显式定义了除默认构造函数以外的任何构造函数,则将是这种情况(不会生成默认ctor)。如果我们太讲究教学,我们可能会比帮助更多的感到困惑,而泰勒(Tyler)在之前对我的回复中对此做了很好的说明。
stinky472 2010年

8
@ wiz-loz我会说foo确实有一个构造函数,它只是隐式的。但这确实是语义论证。
泰勒·麦克亨利

4
我将“默认构造函数”解释为无需参数即可调用的构造函数。这可以是您自己定义的,也可以是编译器隐式生成的。因此,缺少它意味着既不是您自己定义的,也不是生成的。或我就是这样看的。
2010年

28

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

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成员由具有两个输入迭代器std::string构造函数初始化,该rname成员使用对的引用进行初始化name,该crname成员使用对的const-引用进行初始化name,并且该age成员使用value进行初始化-4

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

当您不指定成员的mem-initializer时,C ++标准会说:

如果实体是类类型为...的非静态数据成员,则该实体将进行默认初始化(8.5)。...否则,实体未初始化。

在这里,由于name是类类型的非静态数据成员,因此如果未namemem-initializer-list中指定初始化器,则将默认初始化。的所有其他成员Example没有类类型,因此它们没有初始化。

当标准说它们没有被初始化时,这意味着它们可以具有任何值。因此,由于上述代码未初始化pname,因此可以是任何东西。

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


当您想要严格分开声明(in .h)和定义(in .cpp)而不显示过多内部信息时,这是初始化成员的最佳方法。
Matthieu

12

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

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

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


9

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

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

对于本身是类实例的成员,将调用其默认构造函数,因此将初始化您的字符串对象。

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

我进行了实验,string name初始化堆栈上的类后,它似乎为空。您绝对确定答案吗?
bodacydo 2010年

1
字符串将具有默认情况下提供空字符串的构造函数-我将澄清我的答案
保罗·迪克森

@bodacydo:Paul是正确的,但是如果您关心这种行为,将其露骨无疑是没有害处的。将其扔到初始化程序列表中。
斯蒂芬2010年

感谢您的澄清和解释!
bodacydo 2010年

2
这不是随机的!随机是这个大词!如果标量成员是随机的,则不需要任何其他随机数生成器。想象一下一个程序,该程序分析数据“剩余”(如内存中的未删除文件),而数据远非随机。甚至没有定义!通常很难定义,因为通常我们不知道我们的机器做什么。如果您刚刚删除的那个“随机数据”是您父亲的唯一图像,那么如果您说它的随机性,您的母亲甚至可能会发现它令人反感……
slyy2048

5

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

当然,对于对象参数(如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

2

具有构造函数的成员将具有其默认构造函数,要求进行初始化。

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


0

如果它在堆栈上,则没有自己的构造函数的未初始化成员的内容将是随机且未定义的。即使是全球性的,依靠它们为零也是一个坏主意。无论它是否在堆栈上,如果成员具有自己的构造函数,都将调用该函数对其进行初始化。

因此,如果您具有string * pname,则指针将包含随机垃圾。但对于字符串名称,将调用string的默认构造函数,从而为您提供一个空字符串。对于您的引用类型变量,我不确定,但是它可能是对某些随机内存块的引用。


0

这取决于类的构造方式

回答这个问题需要了解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 ”:否(POD的定义本身就是一个巨大的switch语句)
  • “如果T是数组类型”:否
  • “否则,什么也没做”:因此它带有未定义的值

然后,如果有人决定对值进行初始化,我们转到https://en.cppreference.com/w/cpp/language/value_initialization “值初始化的作用是”并启动case语句:

  • “如果T是没有默认构造函数或具有用户提供或删除的默认构造函数的类类型”:情况并非如此。现在,您将花费20分钟来搜索以下条款:
    • 我们有一个隐式定义的默认构造函数(特别是因为没有定义其他构造函数)
    • 它不是用户提供的(隐式定义)
    • 它没有被删除(= delete
  • “如果T是具有默认构造函数的类类型,该构造函数既不是用户提供的也不是删除的”:是
    • “对象是零初始化的,如果它具有非平凡的默认构造函数,则将其默认初始化”:没有非平凡的构造函数,只是零初始化。“ zero-initialize”的定义至少很简单,并且可以满足您的期望:https : //en.cppreference.com/w/cpp/language/zero_initialization

这就是为什么我强烈建议您不要依赖“隐式”零初始化的原因。除非有很强的性能原因,否则在构造函数(如果已定义)上或使用聚合初始化显式初始化所有内容。否则,对于未来的开发人员来说,事情变得非常危险。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.