会员没有归零,一个clang ++ bug?

时间:2014-02-21 11:00:31

标签: c++ c++11 clang clang++

请考虑以下代码:

class A {
public:
    int i;
    A() {}
};

class B {
public:
    A a;
    int i;
};

int main() {
    B* p = new B {};
    std::cout << p->i << " " << p->a.i << "\n";
}

在clang ++中使用-std = c ++ 11编译,p->i结果为零,但p->a.i没有。只要它的类没有用户提供的构造函数,整个对象是否应该归零?

编辑:由于评论中有一些广泛的讨论,我认为最好在标准中添加一些摘录:

  

T类型的对象进行值初始化意味着:

     
      
  • 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(并且如果初始化是错误的,则T没有可访问的默认构造函数);
  •   
  • 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T隐式声明的默认值构造函数是非平凡的,该构造函数被调用。
  •   
  • 如果T是数组类型,则每个元素都是值初始化的;
  •   
  • 否则,该对象为零初始化。
  •   
     

零初始化T类型的对象或引用意味着:

     
      
  • 如果T是标量类型(3.9),则将对象设置为值0(零),作为整数常量表达式,转换为T;
  •   
  • 如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;
  •   
  • 如果T是一个(可能是cv限定的)联合类型,则该对象的第一个非静态命名数据成员被零初始化,并且填充被初始化为零位;
  •   
  • 如果T是数组类型,则每个元素都是零初始化的;
  •   
  • 如果T是引用类型,则不执行初始化。
  •   

每个的第二个子弹都适用于此。

4 个答案:

答案 0 :(得分:13)

根据C ++ 11标准加相关DR

,Clang是正确的

在最初的C ++ 11规范中,B{}将执行值初始化,导致a.i被零初始化。对于像

这样的情况,与C ++ 98相比,这是一种行为上的变化
B b = {};

...在C ++ 98中作为聚合初始化处理,但在C ++ 11 FDIS中被视为值初始化。

但是,此情况下的行为由core issue 1301更改,它通过强制在 braced-init-list <初始化聚合时使用聚合初始化来恢复C ++ 98行为/ em>的。由于此问题被视为DR,因此它被视为事实上应用于C ++标准的早期版本,因此符合要求的C ++ 11编译器将在此处执行聚合初始化而不是值 - 初始化。

最终,依靠值初始化来初始化数据成员是一个坏主意,尤其是对于具有用户提供的构造函数的类。

答案 1 :(得分:6)

它确实看起来像一个错误(或者,正如评论中指出的那样,尽管指定了C ++ 11,但仍按照C ++ 03行事)。在C ++ 11中,值初始化应该在调用其默认构造函数之前将a的成员归零。 B的初始化受此8.5 / 7

规则的约束
  

如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么对象是零初始化,如果T的隐式声明的默认构造函数是非-trivial,调用该构造函数。

零初始化应该按照8.5 / 5的规则递归零初始化a

  

如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化

当然,a的零初始化应将i设置为零。

答案 2 :(得分:5)

编译器错误,这是代码中的错误。编译器似乎正在实现C ++ 03行为,但这在C ++ 11中发生了重大变化。

这些是来自C ++ 03和C ++ 11标准的一些相关引用

在C ++ 03中:

  

对T类型的对象进行值初始化意味着:

     

- 如果T是类类型   (第9节)用户声明的构造函数(12.1),然后是默认值   调用T的构造函数(如果T,初始化是错误的   没有可访问的默认构造函数);

     

- 如果T是非联合类   没有用户声明的构造函数的类型,然后是每个非静态数据   T的成员和基类组件是值初始化的;

(强调我的)

在C ++ 11中:

  

对T类型的对象进行值初始化意味着:

     

- 如果T是a(可能是   cv-qualified)带有用户提供的构造函数的类类型(第9节)   (12.1),然后调用T的默认构造函数(和   如果T没有可访问的默认值,则初始化是错误的   构造函数);

     

- 如果T是(可能是cv限定的)非联合类类型   没有用户提供的构造函数,那么对象就是   零初始化,如果T是隐式声明的默认构造函数   是非平凡的,这个构造函数被调用。

  

零初始化T类型的对象或引用意味着:

     

- 如果T是a   标量类型(3.9),对象设置为值0(零),取为   一个整数常量表达式,转换为T;

     
      
  • 如果T是   (可能是cv限定的)非联合类类型,每个非静态数据   成员和每个基类子对象都是零初始化和填充   被初始化为零位;
  •   

注意:以下内容仅适用于C ++ 03

删除A用户提供的构造函数,或将其更改为

A() : i() {}

当您初始化 B时,

B* p = new B {};

值初始化其数据成员。由于A具有默认构造函数,因此值初始化会导致对该调用的调用。但是该构造函数没有显式初始化A::i,因此它得到 default-initialized ,对于int,它意味着不执行初始化。

如果您没有为A然后提供默认构造函数,则在A值初始化时,数据成员将被初始化为零。

答案 3 :(得分:0)

不需要将积分类型初始化为非默认构造函数中的值(因为您提供了构造函数)

将构造函数更改为A() : i(0) {}