默认值,值和零初始化混乱

时间:2015-04-21 07:44:50

标签: c++ c++11 c++14 c++03 c++98

我对价值感到非常困惑 - &默认 - &零初始化。 特别是当他们为不同的标准 C ++ 03 C ++ 11 (以及 C ++ 14 )提供服务时。

我引用并试图在这里扩展一个非常好的答案Value-/Default-/Zero- Init C++98 and C++03以使其更加通用,因为它可以帮助很多用户,如果有人可以帮助填写所需的差距,以便对发生的事情有一个很好的概述当?

简要说明一下示例的完整见解:

有时新操作员返回的内存会被初始化,有时候根据您正在新建的类型是POD (plain old data)还是#{3}},或者它是否为“sa”来实现。包含POD成员并使用编译器生成的默认构造函数的类。

  • C ++ 1998 中,有两种类型的初始化:零 - 默认初始化
  • C ++ 2003 第三种初始化中,添加了值初始化
  • C ++ 2011 / C ++ 2014 中,仅添加列表初始化,并且 value- / default- / zero-initialization <的规则< / em>改了一下。

假设:

struct A { int m; };                     
struct B { ~B(); int m; };               
struct C { C() : m(){}; ~C(); int m; };  
struct D { D(){}; int m; };             
struct E { E() = default; int m;} /** only possible in c++11/14 */  
struct F {F(); int m;}  F::F() = default; /** only possible in c++11/14 */

在C ++ 98编译器中,应发生以下情况

  • new A - 不确定值(A是POD)
  • new A() - 零初始化
  • new B - 默认构造(B::m未初始化,B为非POD)
  • new B() - 默认构造(B::m未初始化)
  • new C - 默认构造(C::m为零初始化,C为非POD)
  • new C() - 默认构造(C::m为零初始化)
  • new D - 默认构造(D::m未初始化,D为非POD)
  • new D() - 默认构造?D::m未初始化)

在符合C ++ 03的编译器中,事情应该如下:

  • new A - 不确定值(A是POD)
  • new A() - value-initialize A,这是零初始化,因为它是POD。
  • new B - 默认初始化(离开B::m未初始化,B为非POD)
  • new B() - value-initializes B,它对所有字段进行零初始化,因为它的默认ctor是编译器生成的而不是用户定义的。
  • new C - 默认初始化C,调用默认的ctor。 (C::m为零初始化,C为非POD)
  • new C() - value-initializes C,调用默认的ctor。 (C::m为零初始化)
  • new D - 默认构造(D::m未初始化,D为非POD)
  • new D() - value-initializes D?,调用默认的ctor(D::m未初始化)

斜体值和?是不确定的,请帮助纠正这个: - )

在符合C ++ 11标准的编译器中,事情应该是这样的:

??? (如果我从这里开始,请帮助,无论如何都会出错)

在符合C ++ 14的编译器中,事情应该如下:  ??? (如果我从这里开始请帮忙,无论如何都会出错)  (基于答案的草案)

  • new A - 默认初始化A,编译器gen。 ctor,(leavs A::m未初始化)(A是POD)
  • new A() - value-initializes A,这是从 [dcl.init] / 8

    < / LI>
  • new B - 默认初始化B,编译器gen。 ctor,(leavs B::m未初始化)(B是非POD)

  • new B() - value-initializes B,它对所有字段进行零初始化,因为它的默认ctor是编译器生成的而不是用户定义的。
  • new C - 默认初始化C,调用默认的ctor。 (C::m为零初始化,C为非POD)
  • new C() - value-initializes C,调用默认的ctor。 (C::m为零初始化)
  • new D - 默认初始化DD::m未初始化,D为非POD)
  • new D() - 值初始化D,调用默认的ctor(D::m未初始化)
  • new E - 默认初始化E,调用comp。根。构造函数。 (E::m未初始化,E为非POD)
  • new E() - 值初始化E,从 [dcl.init] / 8 中的2点开始归零E
  • new F - 默认初始化F,调用comp。根。构造函数。 (F::m未初始化,F为非POD)
  • new F() - 值初始化F默认初始化 F,因为1.指向 [dcl.init] / 8 < / em>(F ctor函数是用户提供的,如果它是用户声明的,并且在第一次声明时未明确默认或删除。Link

2 个答案:

答案 0 :(得分:24)

C ++ 14指定在[expr.new] / 17(C ++ 11中的[expr.new] / 15)中使用new创建的对象的初始化,并且该注释不是注意但是当时的规范性文本):

  

创建类型为T的对象的 new-expression 初始化它   对象如下:

     
      
  • 如果省略 new-initializer ,则对象默认初始化(8.5)。 [注意:如果没有初始化       执行后,该对象具有不确定的值。 - 结束说明]
  •   
  • 否则, new-initializer 将根据8.5的 direct-initialization 的初始化规则进行解释。
  •   

默认初始化在[dcl.init] / 7中定义(C ++ 11中为/ 6,并且措辞本身具有相同的效果):

  

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

     
      
  • 如果T是(可能是cv限定的)类类型(第9条),则调用T的默认构造函数(12.1)(并初始化)   如果T没有默认构造函数或重载决策,则格式错误   (13.3)导致歧义或被删除的函数或   从初始化的上下文无法访问);
  •   
  • 如果T是数组类型,则每个元素都是默认初始化;
  •   
  • 否则,不执行初始化。
  •   

因此

  • new A仅会导致调用A的默认构造函数,而不会初始化m。不确定的价值。 new B
  • 应该相同
  • new A()根据[dcl.init] / 11(C ++ 11中的/ 10)进行解释:

      

    一个对象,其初始化程序是一组空的括号,即(),应进行值初始化。

    现在考虑[dcl.init] / 8(C ++ 11†中的/ 7):

      

    value-initialize T类型的对象意味着:

         
        
    • 如果T是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或者是默认构造函数   用户提供或删除,然后该对象被默认初始化;
    •   
    • 如果T是一个(可能是cv限定的)类类型而没有用户提供或删除的默认构造函数,那么对象是   零初始化和语义约束   检查默认初始化,如果T具有非平凡的默认值   构造函数,该对象是默认初始化的;
    •   
    • 如果T是数组类型,则每个元素都是值初始化的;
    •   
    • 否则,该对象为零初始化。
    •   

    因此new A()将零初始化m。对于AB

  • ,这应该是等效的
  • new Cnew C()将默认初始化对象,因为最后一个引用的第一个项目符号点适用(C具有用户提供的默认构造函数!)。但是,显然,现在m在两种情况下都在构造函数中初始化。


†嗯,这一段在C ++ 11中的措辞略有不同,不会改变结果:

  

value-initialize T类型的对象意味着:

     
      
  • 如果T是一个(可能是cv限定的)类类型(第9条),带有   用户提供的构造函数(12.1),然后是T的默认构造函数   被调用(如果T无法访问,则初始化是错误的   默认构造函数);
  •   
  • 如果T是(可能是cv合格的)非工会   类型没有用户提供的构造函数,那么对象就是   零初始化,如果T隐式声明的默认构造函数   是非平凡的,那个构造函数被调用。
  •   
  • 如果T是数组类型,   然后每个元素都进行了值初始化;
  •   
  • 否则,对象是   初始化为零。
  •   

答案 1 :(得分:0)

我可以确认,在C ++ 11中,至少在编译器实现方面,C ++ 14问题中提到的所有内容都是正确的。

为了验证这一点,我在我的test suite中添加了以下代码。我在GCC 7.4.0,GCC 5.4.0,Clang 10.0.1和VS 2017中使用-std=c++11 -O3进行了测试,下面的所有测试都通过了。

#include <gtest/gtest.h>
#include <memory>

struct A { int m;                    };
struct B { int m;            ~B(){}; };
struct C { int m; C():m(){}; ~C(){}; };
struct D { int m; D(){};             };
struct E { int m; E() = default;     };
struct F { int m; F();               }; F::F() = default;

// We use this macro to fill stack memory with something else than 0.
// Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but
// pass in practice, and help illustrate that `a.m` is indeed not initialized
// to zero. Note that we initially tried the more aggressive test
// EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to
// 42, but was still equal to some garbage value, not zero).
//
#define FILL { int m = 42; EXPECT_EQ(m, 42); }

// We use this macro to fill heap memory with something else than 0, before
// doing a placement new at that same exact location. Subsequent calls to
// EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice,
// and help illustrate that `a->m` is indeed not initialized to zero.
//
#define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42)

TEST(TestZero, StackDefaultInitialization)
{
    { FILL; A a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; B a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; C a; EXPECT_EQ(a.m, 0); }
    { FILL; D a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; F a; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackValueInitialization)
{
    { FILL; A a = A(); EXPECT_EQ(a.m, 0); }
    { FILL; B a = B(); EXPECT_EQ(a.m, 0); }
    { FILL; C a = C(); EXPECT_EQ(a.m, 0); }
    { FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a = E(); EXPECT_EQ(a.m, 0); }
    { FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackListInitialization)
{
    { FILL; A a{}; EXPECT_EQ(a.m, 0); }
    { FILL; B a{}; EXPECT_EQ(a.m, 0); }
    { FILL; C a{}; EXPECT_EQ(a.m, 0); }
    { FILL; D a{}; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a{}; EXPECT_EQ(a.m, 0); }
    { FILL; F a{}; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, HeapDefaultInitialization)
{
    { FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapValueInitialization)
{
    { FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapListInitialization)
{
    { FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB
}

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

提到UB!的地方是未定义的行为,实际行为可能取决于许多因素(a.m可能等于42、0或其他垃圾)。从理论上讲,~UB的位置也是未定义的行为,但是在实践中,由于使用了新的位置,因此a->m等于42以外的其他值的可能性很小。