【问题标题】:When do I have to use initializer lists for initializing C++ class members?什么时候必须使用初始化列表来初始化 C++ 类成员?
【发布时间】:2009-10-27 17:41:04
【问题描述】:

假设我有 std::map< std::string, std::string > m_someMap作为A类的私有成员变量

两个问题:(我问的唯一原因是因为我遇到了这样的代码)

  1. 这一行的目的是什么:

    A::A() : m_someMap()
    

    现在我知道这是初始化,但你必须这样做吗? 我很困惑。

  2. std::map< std::string, std::string > m_someMap 的默认值是多少,C# 还定义 int、double 等始终初始化为默认 0,对象为 null(至少在大多数情况下) 那么 C++ 中的规则是什么?对象是否由默认为 null 和原语初始化为垃圾? 当然,我正在讨论实例变量。

编辑:

另外,既然大多数人指出这是一种风格选择而不是必要的,那么:

A::A() : m_someMap(), m_someint(0), m_somebool(false)

【问题讨论】:

标签: c++ stl map initialization


【解决方案1】:

m_somemap

  1. 您不必这样做。
  2. 如果省略它会得到什么:一个空的std::map< std::string, std::string >,即该地图的一个有效实例,其中没有任何元素。

m_somebool

  1. 如果您希望它具有已知值,则必须将其初始化为 truefalse。布尔值是“普通的旧数据类型”,它们没有构造函数的概念。此外,C++ 语言没有为非显式初始化的布尔值指定默认值。
  2. 如果省略它会得到什么:具有未指定值的布尔成员。您不能这样做,以后再使用它的值。因此,强烈建议您初始化此类型的所有值。

m_someint

  1. 如果您希望它具有已知值,则必须将其初始化为某个整数值。整数是“普通的旧数据类型”,它们没有构造函数的概念。此外,C++ 语言没有为非显式初始化的整数指定默认值。
  2. 如果省略它会得到什么:具有未指定值的 int 成员。您不能这样做,以后再使用它的值。因此,强烈建议您初始化此类型的所有值。

【讨论】:

  • 您的第二部分可能更好地表述为“它调用 ::std::map 的默认构造函数。第一个可以详细说明为“您不必这样做,但这是一种风格选择。就我个人而言,我认为这是一个 的。”。 (我自己认为这很愚蠢和毫无意义。)
  • 哦,哎呀,第二部分我错了。你完全正确。
  • 我认为你的措辞更好,我已经编辑了!我把它带回了原来的样子。至于第一部分,“你不必”已经暗示你可以做到,如果你愿意,那就没必要了。否则会使用“You must not do it”或“You can't do it”,事实并非如此。
  • 怎么样:m_someMap()、m_someint(0)、m_somebool(false)(阅读编辑)
  • @Martin York:我改写了。我当然不会将“对象”一词应用于这些,尽管它们可能是 C++ 标准术语中的正式对象,我不知道。
【解决方案2】:

实际上没有必要这样做。
默认构造函数会自动执行。

但有时通过明确说明它可以充当某种文档:

class X
{
    std::map<string,string>  data;
    Y                        somePropertyOfdata;

    X()
      :data()                    // Technically not needed
      ,somePropertyOfdata(data)  // But it documents that data is finished construction
    {}                           // before it is used here.
};

C++ 中的规则是,除非您显式初始化 POD 数据,否则它是未定义的,而其他类会自动调用默认构造函数(即使程序员没有显式这样做)。

但是这么说。考虑一下:

template<typename T>
class Z
{
     T  data;   
     Z()
        :data()    // Technicall not need as default constructor will
                   // always be called for classes.
                   // But doing this will initialize POD data correctly
                   // if T is a basic POD type. 
     {}
};

在这里你会期望数据被默认初始化。
从技术上讲,POD 没有构造函数,所以如果 T 是 int 那么你会期望它做任何事情吗?因为它是显式初始化的,所以它被设置为 0 或 POD 类型的等价物。

对于编辑:

class A
{
    std::map<string,string>   m_someMap;
    int                       m_someint;
    bool                      m_somebool;
   public:
    A::A()
       : m_someMap()      // Class will always be initialised (so optional)
       , m_someint(0)     // without this POD will be undefined
       , m_somebool(false)// without this POD will be undefined
    {}
};

【讨论】:

  • 如果您有相互依赖的成员,请注意避免初始化顺序问题。如果在第一个示例中更改了datasomePropertyOfData声明顺序,那么您正在召唤鼻恶魔。
  • @D,Shawley:是的。但是所有好的编译器都会产生适当的警告。如果您是优秀的程序员,您可以将编译器设置为将所有警告报告为错误,因此它不会编译。
  • 哇,在 POD 类型上显式调用默认构造函数的规则非常奇怪。如果 T 是具有自动生成的默认构造函数的类会发生什么?
  • @Omnifarious:你觉得会发生什么?默认构造函数总是被调用,即使它是编译器生成的。
  • 在最后一个示例中,我更愿意说“未定义”而不是“(即随机)”。随机表明这可能会有所帮助。添加一些关于静态和常量 POD 之间区别的信息可能会很有用。
【解决方案3】:

正如其他人指出的那样:这不是必需的,而是或多或少的风格问题。 好处:它表明您明确希望使用默认构造函数并使您的代码更加冗长。缺点:如果您有多个 ctor,维护所有这些更改可能会很痛苦,有时您添加类成员却忘记将它们添加到 ctors 初始化器列表中并使其看起来不一致。

【讨论】:

    【解决方案4】:
    A::A() : m_someMap()
    

    在这种情况下,此行是不必要的。然而,一般来说,这是初始化类成员的唯一正确方法。

    如果你有这样的构造函数:

    X() : y(z) {
     w = 42;
    }
    

    那么在调用X 构造函数时会发生以下情况:

    • 首先,初始化所有成员:对于y,我们明确表示我们希望调用以z 作为参数的构造函数。对于w,会发生什么取决于w 的类型。如果w 是 POD 类型(即基本上是 C 兼容类型:无继承,无构造函数或析构函数,所有成员为 public,所有成员也是 POD 类型),那么它是 not em> 已初始化。它的初始值是在该内存地址找到的任何垃圾。如果w 是非 POD 类型,则调用其默认构造函数(非 POD 类型总是在构造时初始化)。
    • 一旦两个成员都构建完成,我们然后调用赋值运算符将 42 赋值给w

    需要注意的重要一点是,在我们进入构造函数的主体之前,调用所有构造函数。一旦我们进入身体,所有的成员都已经被初始化了。 所以我们的构造函数体有两个可能的问题。

    • 如果w 属于没有默认构造函数的类型怎么办?那么这将无法编译。然后它必须:之后显式初始化,就像y一样。
    • 如果调用 both 默认构造函数和赋值运算符的顺序不必要地慢怎么办?或许直接调用正确的构造函数会更有效率。

    所以简而言之,由于m_someMap是一个非POD类型,严格来说我们不需要做: m_someMap()。无论如何,它都是默认构造的。但如果它是 POD 类型,或者如果我们想调用另一个构造函数而不是默认构造函数,那么我们就需要这样做。

    【讨论】:

    • 什么是 POD
    【解决方案5】:

    只是为了弄清楚发生了什么(关于你的第二个问题)

    std::map&lt; std::string, std::string &gt; m_someMap 创建一个名为 m_someMap 的堆栈变量,并在其上调用默认构造函数。所有对象的 C++ 规则是:

    T varName;
    

    其中 T 是一个类型,varName 是默认构造的。

    T* varName;
    

    在新标准中应显式分配给 NULL(或 0)——或 nullptr。

    【讨论】:

    • 对于T varName,它并不总是默认构造的——如果Tintstruct Foo { int Bar; };,则不会是这样。
    【解决方案6】:

    澄清默认值问题:

    C++ 没有某些类型隐含地被引用的概念。除非某些东西被显式声明为指针,否则它不能采用空值。这意味着当没有指定构造函数参数时,每个 类都会有一个默认构造函数来构建初始值。如果没有声明默认构造函数,编译器会为你生成一个。此外,每当一个类包含属于分类类型的成员时,这些成员将在对象构造时通过它们自己的默认构造函数隐式初始化,除非您使用冒号语法显式调用不同的构造函数。 p>

    碰巧所有 STL 容器类型的默认构造函数都构建了一个空容器。其他类可能对它们的默认构造函数有其他约定,所以你仍然想知道它们是在这种情况下被调用的。这就是为什么 A::A() : m_someMap() 行,它实际上只是告诉编译器做它已经做的事情。

    【讨论】:

      【解决方案7】:

      当您在 C++ 中创建对象时,构造函数会按照以下顺序进行:

      1. 调用整个类树中所有父虚拟类的构造函数(顺序任意)
      2. 按声明顺序调用所有直接继承的父类的构造函数
      3. 按声明顺序调用所有成员变量的构造函数

      还有一些比这更多的细节,一些编译器允许你强制一些事情超出这个特定的顺序,但这是一般的想法。对于这些构造函数调用中的每一个,您都可以指定构造函数参数,在这种情况下,C++ 将按指定调用构造函数,或者您可以不理会它,C++ 将尝试调用默认构造函数。默认构造函数就是不带参数的构造函数。

      如果您的任何虚拟父类、非虚拟父类或成员变量没有默认构造函数或需要使用非默认构造函数创建,则将它们添加到构造函数调用列表中。因为 C++ 假定默认构造函数调用,所以将默认构造函数放在列表中和完全不使用它之间绝对没有区别(C++ 不会(除非在此问题范围之外的特殊情况下)在不调用 a 的情况下创建对象某种构造函数)。如果一个类没有默认构造函数并且你没有提供构造函数调用,编译器会抛出错误。

      当涉及到诸如floatint 之类的内置类型时,默认构造函数根本不执行任何操作,因此该变量将具有内存中剩余的任何内容的默认值。所有内置类型也都有一个复制构造函数,因此您可以通过将它们的初始值作为唯一参数传递给变量的构造函数来初始化它们。

      【讨论】:

        猜你喜欢
        • 2023-03-18
        • 1970-01-01
        • 2019-09-10
        • 2011-12-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多