为什么委派给默认构造函数的初始化成员变量不为零

时间:2018-07-11 09:56:43

标签: c++11

#include <string>

struct T1 {
  int _mem1;
  int _mem2;
  T1() = default;
  T1(int mem2) : T1() { _mem2 = mem2; }
};
T1 getT1() { return T1(); }
T1 getT1(int mem2) { return T1(mem2); }
int main() {
  volatile T1 a = T1();
  std::printf("a._mem1=%d a._mem2=%d\n", a._mem1, a._mem2);
  volatile T1 b = T1(1);
  std::printf("b._mem1=%d b._mem2=%d\n", b._mem1, b._mem2);
  // Temporarily disable
  if (false) {
    volatile T1 c = getT1();
    std::printf("c._mem1=%d c._mem2=%d\n", c._mem1, c._mem2);
    volatile T1 d = getT1(1);
    std::printf("d._mem1=%d d._mem2=%d\n", d._mem1, d._mem2);
  }
}

当我使用gcc5.4进行编译时,得到以下输出:

g++ -std=c++11 -O3 test.cpp -o test && ./test
a._mem1=0 a._mem2=0
b._mem1=382685824 b._mem2=1

为什么b的委派给默认构造函数的用户定义构造函数无法将_mem1设置为零,而使用默认构造函数的a却被初始化为零?

Valgrind也确认了这一点:

==12579== Conditional jump or move depends on uninitialised value(s)
==12579==    at 0x4E87CE2: vfprintf (vfprintf.c:1631)
==12579==    by 0x4E8F898: printf (printf.c:33)
==12579==    by 0x4005F3: main (in test)

如果我将if(false)更改为if(true)

然后输出与您期望的一样

a._mem1=0 a._mem2=0
b._mem1=0 b._mem2=1
c._mem1=0 c._mem2=0
d._mem1=0 d._mem2=1

编译器在做什么?

2 个答案:

答案 0 :(得分:2)

简短答案:对于琐碎的类型,两种不同形式的“默认构造”会导致两种不同的初始化:

  • T a;,在这种情况下,对象是默认初始化的。其值不确定,未定义的行为将很快发生(这是初始化b.mem1的方式以及valgrind检测错误的原因。)
  • T a=T();,在这种情况下,对象被值初始化,并且其整个内存被清零(a.mem1a.mem2会发生这种情况)

长答案:实际上,T1的默认构造函数不是a.mem1初始化为零的原因。 a首先进行了零初始化,但未进行b初始化,因为该标准的单一规则不适用于b的初始化程序。

定义volatile a=T()导致avalue-initialized (1)struct T1作为否user-provided的默认构造函数(2)。对于这种结构,整个对象是zero-initialized,如C ++ 11标准[dcl.init]/7.2的此规则所述:

  

如果T是(可能是cv限定的)非工会类类型,而没有用户提供的构造函数,则该对象将初始化为零,并且,如果T的隐式声明的默认构造函数不是平凡的,则将该构造函数称为


C ++ 11和C ++ 17之间存在细微的差异,导致定义volatile b=T(1)在C ++ 11中是未定义行为,但在C ++ 17中不是。在C ++ 11中,通过复制对象类型b初始化T1,该对象类型由表达式T(1)初始化。此副本构造评估T(1).mem1,这是一个未确定的值。这是forbidden。在c ++ 17中,b prvalue 表达式T(1)直接初始化。

printf内部对此不确定值的评估也是未定义行为,独立于c ++标准。这就是valgrind抱怨的原因,并且当您将if (true)更改为if (false)时会看到不一致的输出。

(1)严格来说,a是从c ++ 11中的值初始化对象构造而成的副本

(2)T1的默认构造函数不是用户提供的,因为它在第一个声明中被定义为默认构造

答案 1 :(得分:0)

简短答案 您代码中的default构造函数被认为是琐碎的,并且这种构造函数不执行任何操作,即保持事物统一。

更长的答案

  

临时默认构造函数

     

类T的默认构造函数很简单(即不执行   操作)是否满足以下所有条件:

The constructor is not user-provided (i.e., is implicitly-defined or defaulted on its first declaration)
T has no virtual member functions
T has no virtual base classes 

T has no non-static members with default initializers. 
     

(自C ++ 11起)

Every direct base of T has a trivial default constructor
Every non-static member of class type has a trivial default constructor 
     

>普通的默认构造函数是不执行任何操作的构造函数   操作。。所有与C语言兼容的数据类型(POD类型)都是   可以默认构造的。但是,与C语言不同,带有   简单的默认构造函数不能简单地创建   重新解释适当对齐的存储,例如分配给   std :: malloc:正式引入新的位置需要放置新的   反对并避免潜在的不确定行为。

http://www.enseignement.polytechnique.fr/informatique/INF478/docs/Cpp/en/cpp/language/default_constructor.html