为每个没有定义一个类的类合成默认构造函数是真的吗?

时间:2012-03-09 14:34:02

标签: c++

如果类没有构造函数,编译器会为它创建一个默认构造函数吗?

  

C ++新手的程序员经常有两个常见的误解:

     

为每个类合成默认构造函数   没有定义一个

来自C ++对象模型内容

我不知所措......

6 个答案:

答案 0 :(得分:14)

在引用此引用的部分中对此进行了详细说明。我不会完整地解释它,但这里是部分内容的简短摘要。

首先,您需要了解以下术语:implicitly-declaredimplicitly-definedtrivialnon-trivialsynthesized(使用的术语)作者:Stanley Lippman,但未在标准中使用。)

隐式声明

如果此类中没有implicitly-declared构造函数,则构造函数为user-declared。例如,此类struct T { };未声明任何构造函数,因此编译器隐式声明默认构造函数。另一方面,这个类struct T { T(int); };声明了一个构造函数,因此编译器不会声明一个隐式的默认构造函数。除非您定义了自己的默认构造函数,否则您将无法创建没有参数的T实例。

隐式定义

implicitly-declared构造函数在使用时为implicitly-defined,即在没有参数的情况下创建实例时。假设以下课程struct T { };,行T t;将触发T::T()的定义。否则,您将遇到链接器错误,因为构造函数将被声明但未定义。 但是,隐式定义的构造函数不一定有任何与之关联的代码!默认构造函数是 合成 (意味着创建了一些代码)仅在某些情况下由编译器编写。

琐碎的构造函数

implicitly-declared默认构造函数为trivial时:

  • 它的类没有虚函数,也没有虚基类和
  • 其基类有trivial个构造函数和
  • 所有非静态成员都有trivial个构造函数。

在这种情况下,默认编译器无关,因此没有合成代码。例如,在以下代码中

struct Trivial
{
    int i;
    char * pc;
};

int main()
{
    Trivial t;
}

t的构造不涉及任何操作(您可以通过查看生成的程序集来看到:没有调用构造函数来构造t)。

非平凡

另一方面,如果该类不满足上述三个要求,则其implicitly-declared默认构造函数将为non-trivial,这意味着它将涉及必须执行的一些操作以便尊重语言语义。在这种情况下,编译器将synthesize执行这些操作的构造函数的实现。

例如,请考虑以下类:

struct NonTrivial
{
    virtual void foo();
};

由于它具有虚拟成员函数,因此其默认构造函数必须将虚拟表指针设置为正确的值(假设实现使用虚拟方法表)。

同样,这个类的构造函数

struct NonTrivial
{
    std::string s;
};

必须调用字符串默认构造函数,因为它不是trivial。要执行这些操作,编译器会为默认构造函数生成代码,并在您创建不带参数的实例时随时调用它。您可以通过查看与此实例化NonTrivial n;对应的程序集来检查这一点(您应该看到函数调用,除非构造函数已内联)。


<强>摘要

如果没有为类提供任何构造函数,编译器会隐式声明一个默认构造函数。如果您尝试使用它,编译器会隐式定义它,如果可以的话(例如,当一个类具有非默认构造成员时,它并不总是可行的)。但是,这个隐式定义并不意味着生成任何代码。编译器需要为构造函数生成代码(合成它),只要它是非平凡的,这意味着它涉及实现语言语义所需的某些操作。


<强> N.B。

Stanley B Lippman的“Inside the C ++ object model”,这个答案涉及(可能的)C ++实现,而不是它的语义。因此,上述所有内容都不能推广到所有编译器:据我所知,即使对于一个简单的构造函数,也完全允许实现生成代码。从C ++用户的角度来看,重要的是“隐式声明/定义”方面(以及琐碎/非平凡的区别,因为它具有一些含义(例如,具有非平凡的类的对象)构造函数不能是union的成员))。

答案 1 :(得分:11)

我认为误解是:

  

为每个没有定义

的类合成默认构造函数

如果您不自己声明,那么人们会认为默认的构造函数(不接受任何参数)将始终生成。

然而,事实并非如此,因为如果您自己声明任何构造函数,则不会自动创建默认构造函数。

class MyClass {
public:
    MyClass(int x) {}; // No default constructor will be generated now
};

这会导致像初学者期望使用MyClass这样的问题:

MyClass mc;

哪个不起作用,因为没有默认构造函数不接受args。

编辑,因为OP仍然有点困惑。

想象一下,我上面的MyClass就是这样:

class MyClass {
};

int main() {
    MyClass m;
}

那会编译,因为编译器会自动生成默认构造函数MyClass(),因为使用了MyClass

现在看一下:

#include <iostream>

class MyClass {

};

int main() {
    std::cout << "exiting\n";
}

如果这是唯一的代码,编译器甚至不会生成默认构造函数,因为永远不会使用MyClass

现在这个:

#include <iostream>

class MyClass {
public:
    MyClass(int x = 5) { _x = x; }
    int _x;
};

int main() {
    MyClass m;
    std::cout << m._x;
}

编译器不生成默认构造函数MyClass(),因为该类已经有一个由我定义的构造函数。这将起作用,MyClass(int x = 5)作为默认构造函数,因为它不接受任何参数,但它不是由编译器生成的。

最后,初学者可能遇到问题:

class MyClass() {
public:
    MyClass(int x) { _x = x; }
    int _x;
};

int main() {
    MyClass m;
}

上面的内容会在编译过程中给你一个错误,因为MyClass m需要一个默认的构造函数(没有参数),但是你已经声明了一个带int的构造函数。在这种情况下,编译器也不会生成无参构造函数。

答案 2 :(得分:4)

如果出现以下情况,则为每个未定义一个类的类合成默认构造函数:

  • 使用该类的代码需要一个&amp;只有
  • 您没有为该类明确定义其他构造函数。

答案 3 :(得分:3)

到目前为止,所有赞成的答案似乎都大致相同:

  

为每个没有任何用户定义的构造函数的类合成默认构造函数。

这是对问题中声明的修改,这意味着

  

为每个没有用户定义的默认构造函数的类合成默认构造函数。

差异很重要,但陈述仍然是错误的。

正确的陈述是:

  

为每个没有任何用户定义的构造函数的类合成默认构造函数,并且所有子对象在类的上下文中都是默认构造的。

以下是第一个陈述的一些明确的反例:

struct NoDefaultConstructor
{
     NoDefaultConstructor(int);
};

class Surprise1
{
     NoDefaultConstructor m;
} s1; // fails, no default constructor exists for Surprise1

class Surprise1没有用户定义的构造函数,但没有合成默认构造函数。

子对象是成员还是基础无关紧要:

class Surprise2 : public NoDefaultConstructor
{
} s2; // fails, no default constructor exists for Surprise2

即使所有子对象都是可默认构造的,也必须可以从复合类中访问默认构造函数:

class NonPublicConstructor
{
protected:
    NonPublicConstructor();
};

class Surprise3
{
    NonPublicConstructor m;
} s3; // fails, no default constructor exists for Surprise3

答案 4 :(得分:0)

是的,如果您没有定义自己的构造函数,默认构造函数始终存在(请参阅默认构造函数部分here)。

答案 5 :(得分:0)

http://www.codeguru.com/forum/archive/index.php/t-257648.html

引用:

Stanley B. Lippman撰写的“Inside the C ++ object model”一书中提到了以下内容。

  

编译器的类有四个特征   需要为声明为no的类合成默认构造函数   构造函数。标准将这些视为隐含的,   非平凡的默认构造函数。合成的构造函数完成   只有实施需要。它通过调用成员对象或   基类默认构造函数或初始化虚函数   或每个对象的虚拟基类机制。没有的类   展示这些特征并且根本不宣布构造函数   据说有隐含的,普通的默认构造函数。在实践中,   这些简单的默认构造函数不是合成的。 ...   C ++新手的程序员经常有两个常见的误解:

     
      
  • 为每个类合成默认构造函数   没有定义一个

  •   
  • 编译器合成的默认构造函数提供显式   在类

  • 中声明的每个数据成员的默认初始值设定项   
     

如您所见,这些都不是真的。