成员未清零,是clang++的一个bug吗?

14

考虑以下代码:

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++编译时,p->i的值为零,但p->a.i不是零。只要该类没有用户提供的构造函数,整个对象不应该被清零吗?根据标准中的第二条规定,这里适用于每个子弹。

9
但是类 A 确实有一个用户定义的构造函数。 - icabod
1
不应该。在这种情况下,它调用默认构造函数,并且对A::i没有任何影响。 - juanchopanza
1
但是在这种情况下,“a”的“零初始化”意味着默认构造它,而不是用零填充它,对于一个类来说可能非常无效。 - icabod
1
请注意,B 没有 零初始化。它是 值初始化 - juanchopanza
1
@goodbyeera 零初始化仅适用于可进行零初始化的类型。对于内置类型而言,这是有意义的。然而对于用户定义的类型则很少使用。在您的情况下,A 的值初始化并不会导致其数据成员被零初始化。 - juanchopanza
显示剩余15条评论
4个回答

14

根据C++11标准和相关DRs的规定,Clang是正确的

在最初的C++11规范中,B{}将执行值初始化,导致a.i被零初始化。这与C++98中的情况相比是一种行为变化。

B b = {};

在C++98中,这些都被视为聚合初始化,但在C++11 FDIS中被视为值初始化。然而,由于核心问题1301的更改,这种情况下的行为发生了变化,该问题规定使用聚合初始化来初始化聚合体,通过 大括号初始化列表 初始化。由于该问题被视为DR,因此它被视为实际应用于早期版本的C++标准,所以符合C++11标准的编译器预计会执行聚合初始化,而不是值初始化。

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


这个 CWG 问题 1301成为了 cppreference 开始(或者至少同意开始)将改变行为的 DR 合并到它们所应用的标准中的动力。http://en.cppreference.com/w/Talk:cpp/language/value_initialization - Cubbi

6
确实看起来像是一个bug(或者,正如评论中指出的那样,尽管指定了C++11,但行为仍符合C++03)。在C++11中,值初始化应该在调用其默认构造函数之前将a的成员变量清零。对B的初始化受8.5/7规则的控制。

如果T是一个(可能带有cv限定符的)非联合类类型且没有用户提供的构造函数,则对象被零初始化,并且如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数。

根据8.5/5规则,应递归地对a进行零初始化。

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

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

1
没有用户提供的构造函数 - 但A有一个用户提供的构造函数。 - Suma
@Suma:是的,但B不会这样做,根据第一条规则,在调用构造函数之前应将其初始化为零。 根据第二条规则,应递归地将a初始化为零。 - Mike Seymour
1
@Suma:这正是标准所说的。所有内容都被初始化为零;然后调用“B”的隐式声明构造函数,该构造函数反过来调用“A”的构造函数。 - Mike Seymour
3
明白了,这在C++11中有所改变。在C++03中,如果T是一个非联合类类型且没有用户声明的构造函数,则T的每个非静态数据成员和基类组件都会进行值初始化。 - juanchopanza
2
请查看Richard Smith的答案(https://dev59.com/smEh5IYBdhLWcg3w3mqK#23745872)和他的引用。在相关的DRs下,Clang似乎是正确的。 - bames53
显示剩余2条评论

5

这并不是编译器的错误,而是你代码中的一个错误。编译器似乎正在实现C++03的行为,但是在C++11中已经发生了关键性的变化。

以下是C++03和C++11标准中一些相关的引用:

C++03中:

对于类型T,初始化其对象的值为value-initialize:

- 如果T是类类型(第9条)且具有用户声明的构造函数(12.1),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则该初始化是非法的);

- 如果T是无用户声明构造函数的非联合类类型,则初始化T的每个非静态数据成员和基类组成部分的值为value-initialized;

(强调是我的)

C++11中:

对于类型T,初始化其对象的值为value-initialize:

- 如果T是具有用户提供的构造函数(12.1)的(可能带有cv限定符的)类类型(第9条),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则该初始化是非法的);

- 如果T是无用户提供构造函数的(可能带有cv限定符的)非联合类类型,则将对象zero-initialized,并且如果T的隐式声明默认构造函数是非平凡的,则调用该构造函数。

以及

对于类型T,初始化其对象或引用的值为zero-initialize:

- 如果T是标量类型(3.9),则将对象设置为值0(零),视为整数常量表达式,并转换为T;

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

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

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

A() : i() {}

当您在此处进行值初始化 B 时,
B* p = new B {};

在初始化数据成员时,it会进行值初始化。由于A有默认构造函数,因此值初始化会导致调用该函数。但是该构造函数并未显式初始化A::i,因此它被默认初始化,对于int类型而言,这意味着不执行任何初始化。

如果您没有为A提供默认构造函数,那么当A值初始化时,数据成员会被零初始化。


1
看看Richard Smith的回答(链接为https://dev59.com/smEh5IYBdhLWcg3w3mqK#23745872)以及他提供的参考资料。根据相关DRs,Clang似乎是正确的。 - bames53

0

整型变量不需要像非默认构造函数一样初始化值(因为您已经提供了构造函数)

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接