C ++构造:“MyClass c”不好,“MyClass c = MyClass()”很慢,我想要“MyClass c()”

时间:2013-10-28 02:48:18

标签: c++ constructor

以下是一些代码:

class MyClass
{
public:
    int y;
};

int main()
{
    MyClass item1;
    MyClass item2 = MyClass();
}

当我运行它时,我收到以下值:

item1.y == [garbage]
item2.y == 0

这让我感到惊讶。

我希望item1是默认构造的,而item2是从匿名默认构造的MyClass实例复制构造的,导致两者都等于0(因为default-constructors将成员初始化为默认值)。检查程序集:

//MyClass item1;
//MyClass item2 = MyClass();
xor         eax,eax  
mov         dword ptr [ebp-128h],eax  
mov         ecx,dword ptr [ebp-128h]  
mov         dword ptr [item2],ecx  

通过在临时某处写入'0'值然后将其复制到item2中来显示正在构造的item2,如预期的那样。但是,item1没有任何程序集。

因此,程序在堆栈中有item1的内存,但它从不构造 item1。

我很高兴为了速度目的而想要这种行为,但我想要两全其美!我想知道item1.y == 0(它被构造),但我不想浪费时间在default-construct-anonymous-instance-then-copy-construct上,就像item2那样。

令人沮丧的是,我无法通过说MyClass item1();强制执行默认构造,因为它被解释为函数原型。

所以...如果我想在item1上使用默认构造函数而不使用copy-construct,那我该怎么做呢?

旁注:看起来如果我为MyClass声明一个构造函数,则item1像往常一样构造。所以这种行为只适用于编译器生成的构造函数。

4 个答案:

答案 0 :(得分:16)

让你的课程看起来像这样:

class MyClass 
{
public:
    MyClass() : y(0) 
    {
    }

public:
    int y;
};

两个例子都可以正常使用。您的问题是,如果没有提供构造函数,基本类型成员将进行初始化。所以y表示恰好在item1所在的地点堆栈上的随机数据。

明确地实现构造函数解决了这个问题。

这种行为的存在是因为C ++的“你只需支付你使用的”心态。基本类型没有“默认值”,因为这会使得分配某些东西不必要地(稍微)成本更高,然后由于有效地设置两次值而将其填入。一旦获得“默认值”,一次为您想要的值。

答案 1 :(得分:10)

在C ++中,您只需支付您要求完成的任务。具有不初始化其数据的类型的能力可以是一个重要的性能提升:令人讨厌的是,这也是默认行为,因为它是从C继承的。

您可以通过创建一个初始化int的构造函数,使用{}统一初始化语法,或者在类型声明中使用新的默认语法int y = 0;来解决此问题。最后需要C ++ 11,如果类型是非POD,则第二个需要C ++ 11。

通过检查调试版本程序集,您对Foo f = Foo();的性能问题会产生误导。市场上的每一个非脑死亡编译器都支持复制elision这样一个简单的案例,即使ctor有副作用也是合法的。

答案 2 :(得分:6)

麻烦在于你误解了两件事。

  1. 编译器生成的默认构造函数的作用。
  2. 什么是声明(因此在使用作业时)。
  3. 编译器生成的默认构造函数的作用。

    如果没有定义构造函数,编译器将为您生成默认构造函数。但是有两种不同的版本。 “值初始化”默认构造函数(对于内置POD类型不执行任何操作并使其未初始化)。 “零初始化”默认构造函数(用于内置POD类型将它们设置为零)。

    什么是声明(因此在使用赋值时)。

    赋值运算符仅在=左侧的对象已完全构造时才适用。由于声明中的对象直到';'才完全构造这不是一项任务。

    Bar x = Bar(); // There is no assignment here (this is a declaration using the default constructor
    Bar y = Bar(2);// There is no assignment here (this is a declaration using a constructor).
    

    这是使用复制构造函数从临时构造对象。 这无关紧要,因为编译器只是删除了实际副本并构建到位,所以如果发生任何副本,我会感到非常惊讶。

    您的代码中发生了什么

    int  x;          // default-Inititalized.      [ Value = Garbage]
    int  z = int();  // Zero-Inititalized.         [ Value = 0]
    

    相同的规则适用于具有编译器生成的默认构造函数的类:

    LokiClass  xL;               // Value-Initialized -> Default Initialized
                                 //                     This is an explicit call to 
                                 //                     the default constructor but  
                                 //                     will only Value-Initialize
                                 //                     class types and not initialize
                                 //                     built-in POD types. 
    LokiClass  yL = LokiClass(); // Zero-Initialized    This is an explicit call to 
                                 //                     the default constructor but  
                                 //                     makes sure to use the 
                                 //                     Zero-Initialization version if
                                 //                     it is the compiler generated 
                                 //                     version.
    
    LokiClass  y1L {};           // C++11 version of Zero-Initialization constructor used.
    
    LokiClass  zL((LokiClass()));// This is copy construction
                                 // Which will probably lead to copy elision by the compiler.
    

    那么Value / Zero Initialization之间的区别是什么? 值初始化不会对内置POD类型进行初始化。它将在任何基类和成员上调用Value Initialization编译器生成的默认构造函数(注意,如果你定义一个默认构造函数,那么它将使用它,因为没有编译器生成它)。

    零初始化将对POD类型进行零初始化。它会在任何基类和成员上调用Zero-Initialization编译器生成的默认构造函数(注意,如果你定义了一个默认构造函数,那么它将使用它,因为没有编译器生成它)。

答案 3 :(得分:2)

您可以这样做:

MyClass item1;

根据变量,可能会或可能不会初始化POD类型。

或者如果你使用C ++ 11,你可以这样做:

MyClass item1 {};

显式调用默认构造函数。

没有为item1提供汇编代码的原因是因为编译器认为没有必要为类所示的类生成代码,因为它没有您编写的显式默认构造函数。