【问题标题】:defaulted ctor differences between gcc 4.6 and 4.7gcc 4.6和4.7之间的默认ctor差异
【发布时间】:2013-09-19 17:30:18
【问题描述】:

在 GCC 4.6.1 上,当我声明我自己的类型的实例时,该实例具有默认构造函数,并且如果我实例化该类型的对象并用大括号(如 Foo my_foo{}; )初始化它,POD 成员如果没有声明其他构造函数,则该类中只会进行零初始化。如果除了默认构造函数之外没有其他构造函数,它们将像预期的那样初始化为零。

但是,在 GCC 4.7.3 上,零初始化以任何一种方式发生,这是我所期望的行为。

这里有什么不同?这是编译器错误吗?这两个 GCC 版本都支持 C++11 标准的默认构造函数。

没有必要坚持使用旧的 GCC 版本,但我想了解这里发生了什么。

注意:我默认主 ctor op=。并简单地复制ctor以保持类型可用于可变参数函数(clang要求将类分类为POD,尽管gcc让我摆脱使用可变参数函数的类型,即使使用用户定义的主ctor。如果你能告诉加分我为什么。)

这是一个示例程序来说明,包括底部的一些输出(来自使用两个 GCC 版本编译的二进制文件):

#include <cstdio>

// pod and pod_wctor are identical except that pod_wctor defines another ctor

struct pod {
    pod( void ) = default;
    pod( const pod& other ) = default;
    pod& operator=( const pod& other ) = default;

    int x,y,z;
};

struct pod_wctor {
    pod_wctor( void ) = default;
    pod_wctor( const int setx, const int sety, const int setz ) : x(setx), y(sety), z(setz) { }
    pod_wctor( const pod_wctor& other ) = default;
    pod_wctor& operator=( const pod_wctor& other ) = default;

    int x,y,z;
};

int main ( void ) {

    printf("the following shuold be uninitialized:\n");

    pod pee;
    printf( "    %i,%i,%i\n", pee.x, pee.y, pee.z);

    pod_wctor podtor;
    printf( "    %i,%i,%i\n", podtor.x, podtor.y, podtor.z);

    printf("the following shuold be initialized to 0,0,0:\n");

    pod peenit{};
    printf( "    %i,%i,%i\n", peenit.x, peenit.y, peenit.z );

    pod_wctor podtornit{};
    printf( "    %i,%i,%i\n", podtornit.x, podtornit.y, podtornit.z );

    return 0;

}

// compiled with: g++ m.cpp -std=gnu++0x
// g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 (i386)
/****************** output *******************
the following shuold be uninitialized:
    10381592,134513249,134520820
    134513969,134513504,0
the following shuold be initialized to 0,0,0:
    0,0,0
    7367877,134513945,8724468
*********************************************/

// compiled with: g++ m.cpp -std=gnu++0x
// gcc version 4.7.3 (Ubuntu/Linaro 4.7.3-2ubuntu4) (i386)
/****************** output *******************
the following shuold be uninitialized:
    -1218358300,-1217268232,134520832
    134514450,1,-1079827548
the following shuold be initialized to 0,0,0:
    0,0,0
    0,0,0
*********************************************/

【问题讨论】:

  • 看起来很像早期编译器版本中的一个错误,在后面的版本中已修复。将构造函数添加到类应该会改变使用所述构造函数的代码路径的行为是没有意义的。
  • 当然很明显早期的编译器不符合最终的 C++11 标准(该标准与 GCC 4.6 发布的时间差不多,所以 GCC 4.6 没有时间完全实现一切。)该错误已在以后的编译器中修复。这就是为什么要发布更新版本的编译器的原因:它们实现了新功能并修复了错误。

标签: c++ gcc c++11 default-constructor list-initialization


【解决方案1】:

通过将构造函数 pod_wctor( const int setx, const int sety, const int setz ) : x(setx), y(sety), z(setz) { } 添加到您的类中,它会失去聚合状态:[dcl.init.aggregate]/1

聚合是一个数组或一个类(第 9 条),没有用户提供的构造函数

它仍然是一个 POD,因为 普通类 只需要没有非普通的 default ctors:[class]/6

普通类是具有默认构造函数(12.1)的类,没有非普通默认构造函数, 并且可以轻松复制。


这里有趣的一点是,对于聚合,列表初始化pod peenit{}; 执行聚合初始化:

T 类型的对象或引用的列表初始化定义如下:

  • 如果T 是聚合,则执行聚合初始化(8.5.1)。 [...]
  • 否则,如果初始值设定项列表没有元素且T 是具有默认构造函数的类类型,则对象为值初始化。

(注意:这是修改后的顺序。AFAIK,在标准本身中,这两个点的顺序是颠倒的,这一定是一个缺陷,因为每个聚合都有一个默认的 ctor——隐式声明和定义的一个。)

聚合初始化导致 int 成员的值初始化:[dcl.init.aggr]/7

如果列表中的 initializer-clauses 少于聚合中的成员,则每个未显式初始化的成员都应从空的初始化器列表中初始化

and [dcl.init.list]/3 "否则,如果初始化器列表没有元素,则对象被值初始化"


但是,对于非聚合的pod_wctor,列表初始化pod_wctor podtornit{}直接进行值初始化,它调用了默认的ctor。 [class.ctor]/6 指定:

隐式定义的默认构造函数执行类的一组初始化,这些初始化将由用户编写的该类的默认构造函数执行,没有 ctor-initializer (12.6.2) 和空的复合语句

在 [class.base.init]/8 中,我们发现:

在非委托构造函数中,如果给定的非静态数据成员或基类没有由 mem-initializer-id 指定(包括没有 mem -initializer-list 因为构造函数没有ctor-initializer)并且实体不是抽象类(10.4)的虚拟基类,那么

  • [...]
  • 否则,实体默认初始化 (8.5)。

默认ctor 本身不保证成员归零,因为它只对成员进行默认初始化。


默认初始化和值初始化的区别:[dcl.init]

[7] 默认初始化 T 类型的对象意味着:

  • 如果T 是(可能是cv 限定的)类类型,则T 的默认构造函数称为[...]
  • [...]
  • 否则,不执行初始化。

[...]

[8] 值初始化 T 类型的对象意味着:

  • 如果T 是(可能是cv 限定的)类类型,没有默认构造函数或用户提供或删除的默认构造函数,则该对象被默认初始化;
  • 如果T 是(可能是cv 限定的)非联合类类型,没有用户提供或删除的默认构造函数,则对象为零初始化,如果T有一个重要的默认构造函数,默认初始化;
  • [...]
  • 否则,对象被零初始化。

(我承认这让我很困惑,我不得不修改我的答案。)

pod_wctor 有一个用户提供的默认构造函数。因此,对于列表初始化pod_wctor podtornit{}value-initialization 的第二个项目符号适用。对象podtornit 本身是零初始化,这导致其成员的零初始化。只有 then 它会被默认初始化,并且默认的ctor会被调用。后者什么都不做,但前者保证成员将被归零。

【讨论】:

  • 使用 () 而不是 {} 来初始化 pod_wctor 并在 free store 中使用 new (为了避免“最麻烦的解析”)进行零初始化。我只是使用 {} 来避免 MVP。我还在消化你的帖子。谢谢。
  • 如何用() 初始化pod_wctor?在pod_wctor podtornit = pod_wctor(); 的情况下,这会使用值初始化的临时值进行复制初始化。
  • 在免费商店,就像我说的那样。 pod_wctor *podtornit = 新的 pod_wctor();
  • 啊,我以为这是两种不同的方法(“使用 ()”和“在免费商店”)。两者都进行值初始化。我刚刚发现在一些较早的草稿中(正如我远程猜测的那样),空{} 的值初始化没有特殊情况。遵循这些规范,不会发生归零。 value-init 案例一定出现在 2010 年到 2011 年之间。
猜你喜欢
  • 1970-01-01
  • 2017-10-09
  • 2021-04-27
  • 2012-11-04
  • 2019-06-05
  • 1970-01-01
  • 2017-04-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多