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

Eri*_*c.Q 6 c++

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

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

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

来自C++对象模型内部的书

我很茫然...

Luc*_*lle 14

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

首先,您需要了解以下方面:implicitly-declared,implicitly-defined,trivial,non-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;
}
Run Code Online (Sandbox Code Playgroud)

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

不平凡的

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

例如,考虑以下类:

struct NonTrivial
{
    virtual void foo();
};
Run Code Online (Sandbox Code Playgroud)

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

同样,这个类的构造函数

struct NonTrivial
{
    std::string s;
};
Run Code Online (Sandbox Code Playgroud)

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


摘要

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


NB

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


wkl*_*wkl 11

我认为误解是:

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

人们认为如果你不自己声明默认构造函数,它将不会生成任何参数.

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

class MyClass {
public:
    MyClass(int x) {}; // No default constructor will be generated now
};
Run Code Online (Sandbox Code Playgroud)

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

MyClass mc;
Run Code Online (Sandbox Code Playgroud)

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

编辑 OP仍然有点困惑.

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

class MyClass {
};

int main() {
    MyClass m;
}
Run Code Online (Sandbox Code Playgroud)

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

现在来看看这个:

#include <iostream>

class MyClass {

};

int main() {
    std::cout << "exiting\n";
}
Run Code Online (Sandbox Code Playgroud)

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

现在这个:

#include <iostream>

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

int main() {
    MyClass m;
    std::cout << m._x;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

int main() {
    MyClass m;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码会在编译时抛出一个错误,因为MyClass m需要一个默认的构造函数(没有参数),但是你已经声明了一个构造函数int.在这种情况下,编译器也不会生成无参数构造函数.

  • @ user974349 - C++中的默认构造函数有一个非常清晰的定义:它是一个不接受任何参数的构造函数(或者所有参数都有默认参数). (2认同)