C++11 中的新语法“= default”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20828907/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:21:00  来源:igfitidea点击:

The new syntax "= default" in C++11

c++c++11

提问by user3111311

I don't understand why would I ever do this:

我不明白为什么我会这样做:

struct S { 
    int a; 
    S(int aa) : a(aa) {} 
    S() = default; 
};

Why not just say:

为什么不直接说:

S() {} // instead of S() = default;

why bring in a new syntax for that?

为什么要为此引入新的语法?

回答by Joseph Mansfield

A defaulted default constructor is specifically defined as being the same as a user-defined default constructor with no initialization list and an empty compound statement.

默认的默认构造函数被明确定义为与没有初始化列表和空复合语句的用户定义的默认构造函数相同。

§12.1/6 [class.ctor]A default constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used to create an object of its class type or when it is explicitly defaulted after its first declaration. The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with no ctor-initializer (12.6.2) and an empty compound-statement. [...]

§12.1/6 [class.ctor] 当一个默认构造函数被默认且未被定义为删除时,当它被 odr 用于创建其类类型的对象时,或者当它在第一次声明后被显式默认时,它是隐式定义的。隐式定义的默认构造函数执行类的一组初始化,这些初始化将由用户编写的该类的默认构造函数执行,没有 ctor-initializer (12.6.2) 和空复合语句。[...]

However, while both constructors will behave the same, providing an empty implementation does affect some properties of the class. Giving a user-defined constructor, even though it does nothing, makes the type not an aggregateand also not trivial. If you want your class to be an aggregate or a trivial type (or by transitivity, a POD type), then you need to use = default.

然而,虽然两个构造函数的行为相同,但提供一个空的实现确实会影响类的某些属性。提供用户定义的构造函数,即使它什么都不做,也会使类型不是聚合,也不是平凡的。如果您希望您的类成为聚合或平凡类型(或通过传递性,POD 类型),那么您需要使用= default.

§8.5.1/1 [dcl.init.aggr]An aggregate is an array or a class with no user-provided constructors, [and...]

§8.5.1/1 [dcl.init.aggr]聚合是一个数组或一个类,没有用户提供的构造函数,[和...]

§12.1/5 [class.ctor]A default constructor is trivial if it is not user-provided and [...]

§9/6 [class]A trivial class is a class that has a trivial default constructor and [...]

§12.1/5 [class.ctor]如果默认构造函数不是用户提供的并且 [...]

§9/6 [class]平凡类是具有平凡默认构造函数和 [...]

To demonstrate:

展示:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() { };
};

int main() {
    static_assert(std::is_trivial<X>::value, "X should be trivial");
    static_assert(std::is_pod<X>::value, "X should be POD");

    static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
    static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}

Additionally, explicitly defaulting a constructor will make it constexprif the implicit constructor would have been and will also give it the same exception specification that the implicit constructor would have had. In the case you've given, the implicit constructor would not have been constexpr(because it would leave a data member uninitialized) and it would also have an empty exception specification, so there is no difference. But yes, in the general case you could manually specify constexprand the exception specification to match the implicit constructor.

此外,constexpr如果隐式构造函数已经存在,则显式默认构造函数将使其成为默认构造函数,并且还将为其提供与隐式构造函数相同的异常规范。在您给出的情况下,隐式构造函数不会是constexpr(因为它会留下未初始化的数据成员)并且它也会有一个空的异常规范,所以没有区别。但是是的,在一般情况下,您可以手动指定constexpr异常规范以匹配隐式构造函数。

Using = defaultdoes bring some uniformity, because it can also be used with copy/move constructors and destructors. An empty copy constructor, for example, will not do the same as a defaulted copy constructor (which will perform member-wise copy of its members). Using the = default(or = delete) syntax uniformly for each of these special member functions makes your code easier to read by explicitly stating your intent.

使用= default确实带来了一些统一性,因为它也可以与复制/移动构造函数和析构函数一起使用。例如,一个空的复制构造函数将与默认的复制构造函数不同(它将执行其成员的逐成员复制)。对这些特殊成员函数中的每一个统一使用= default(or = delete) 语法,通过明确说明您的意图,使您的代码更易于阅读。

回答by user3111311

n2210provides some reasons:

n2210提供了一些原因:

The management of defaults has several problems:

  • Constructor definitions are coupled; declaring any constructor suppresses the default constructor.
  • The destructor default is inappropriate to polymorphic classes, requiring an explicit definition.
  • Once a default is suppressed, there is no means to resurrect it.
  • Default implementations are often more efficient than manually specified implementations.
  • Non-default implementations are non-trivial, which affects type semantics, e.g. makes a type non-POD.
  • There is no means to prohibit a special member function or global operator without declaring a (non-trivial) substitute.

默认的管理有几个问题:

  • 构造函数定义是耦合的;声明任何构造函数都会抑制默认构造函数。
  • 析构函数默认值不适用于多态类,需要显式定义。
  • 一旦默认值被抑制,就没有办法恢复它。
  • 默认实现通常比手动指定的实现更有效。
  • 非默认实现是重要的,它会影响类型语义,例如使类型非 POD。
  • 如果不声明(非平凡的)替代品,就无法禁止特殊成员函数或全局运算符。


type::type() = default;
type::type() { x = 3; }

In some cases, the class body can change without requiring a change in member function definition because the default changes with declaration of additional members.

type::type() = default;
type::type() { x = 3; }

在某些情况下,类主体可以在不需要更改成员函数定义的情况下更改,因为默认值会随着其他成员的声明而更改。

See Rule-of-Three becomes Rule-of-Five with C++11?:

看到C++11 的三规则变成五规则了吗?

Note that move constructor and move assignment operator won't be generated for a class that explicitly declares any of the other special member functions, that copy constructor and copy assignment operator won't be generated for a class that explicitly declares a move constructor or move assignment operator, and that a class with a explicitly declared destructor and implicitly defined copy constructor or implicitly defined copy assignment operator is considered deprecated

请注意,不会为显式声明任何其他特殊成员函数的类生成移动构造函数和移动赋值运算符,不会为显式声明移动构造函数或移动的类生成复制构造函数和复制赋值运算符赋值运算符,并且具有显式声明的析构函数和隐式定义的复制构造函数或隐式定义的复制赋值运算符的类被视为已弃用

回答by Slavenskij

I have an example that will show the difference:

我有一个例子可以说明区别:

#include <iostream>

using namespace std;
class A 
{
public:
    int x;
    A(){}
};

class B 
{
public:
    int x;
    B()=default;
};


int main() 
{ 
    int x = 5;
    new(&x)A(); // Call for empty constructor, which does nothing
    cout << x << endl;
    new(&x)B; // Call for default constructor
    cout << x << endl;
    new(&x)B(); // Call for default constructor + Value initialization
    cout << x << endl;
    return 0; 
} 

Output:

输出:

5
5
0

As we can see the call for empty A() constructor does not initialize the members, while B() does it.

正如我们所看到的,对空 A() 构造函数的调用并没有初始化成员,而 B() 会这样做。

回答by Sean Middleditch

It's a matter of semantics in some cases. It's not very obvious with default constructors, but it becomes obvious with other compiler-generated member functions.

在某些情况下,这是语义问题。对于默认构造函数,这不是很明显,但是对于其他编译器生成的成员函数,它变得很明显。

For the default constructor, it would have been possible to make any default constructor with an empty body be considered a candidate for being a trivial constructor, same as using =default. After all, the old empty default constructors were legal C++.

对于默认构造函数,有可能使任何具有空主体的默认构造函数都被视为一个普通构造函数的候选者,就像使用=default. 毕竟,旧的空默认构造函数是合法的 C++

struct S { 
  int a; 
  S() {} // legal C++ 
};

Whether or not the compiler understands this constructor to be trivial is irrelevant in most cases outside of optimizations (manual or compiler ones).

在优化(手动或编译器)之外的大多数情况下,编译器是否理解此构造函数是无关紧要的。

However, this attempt to treat empty function bodies as "default" breaks down entirely for other types of member functions. Consider the copy constructor:

然而,这种将空函数体视为“默认”的尝试对于其他类型的成员函数完全失效。考虑复制构造函数:

struct S { 
  int a; 
  S() {}
  S(const S&) {} // legal, but semantically wrong
};

In the above case, the copy constructor written with an empty body is now wrong. It's no longer actually copying anything. This is a very different set of semantics than the default copy constructor semantics. The desired behavior requires you to write some code:

在上面的例子中,用空体编写的复制构造函数现在是错误的。它不再实际复制任何东西。这是一组与默认复制构造函数语义非常不同的语义。所需的行为需要您编写一些代码:

struct S { 
  int a; 
  S() {}
  S(const S& src) : a(src.a) {} // fixed
};

Even with this simple case, however, it's becoming much more of a burden for the compiler to verify that the copy constructor is identical to the one it would generate itself or for it to see that the copy constructor is trivial(equivalent to a memcpy, basically). The compiler would have to check each member initializer expression and ensure it's identical to the expression to access the source's corresponding member and nothing else, make sure no members are left with non-trivial default construction, etc. It's backwards in a way of the process the compiler would use to verify that it's own generated versions of this function is trivial.

然而,即使在这种简单的情况下,编译器验证复制构造函数与它自己生成的复制构造函数是否相同,或者让编译器看到复制构造函数是微不足道的memcpy基本上相当于)。编译器必须检查每个成员初始值设定项表达式并确保它与访问源的相应成员的表达式相同,而没有其他任何成员,确保没有成员留下非平凡的默认构造等。它在过程中向后编译器会用来验证它自己生成的这个函数的版本是微不足道的。

Consider then the copy assignment operator which can get even hairier, especially in the non-trivial case. It's a ton of boiler-plate that you don't want to have to write for many classes but you're be forced to anyway in C++03:

然后考虑复制赋值运算符,它会变得更加棘手,尤其是在非平凡的情况下。这是一大堆样板,您不想为许多类编写,但无论如何您都必须在 C++03 中编写:

struct T { 
  std::shared_ptr<int> b; 
  T(); // the usual definitions
  T(const T&);
  T& operator=(const T& src) {
    if (this != &src) // not actually needed for this simple example
      b = src.b; // non-trivial operation
    return *this;
};

That is a simple case, but it's already more code than you would ever want to be forced to write for such a simple type as T(especially once we toss move operations into the mix). We can't rely on an empty body meaning "fill in the defaults" because the empty body is already perfectly valid and has a clear meaning. In fact, if the empty body were used to denote "fill in the defaults" then there'd be no way to explicitly make a no-op copy constructor or the like.

这是一个简单的例子,但是对于这样一个简单的类型,它已经比你想被迫编写的代码多T(尤其是当我们将移​​动操作放入混合中时)。我们不能依赖一个空的 body 意思是“填充默认值”,因为空的 body 已经完全有效并且具有明确的含义。事实上,如果空体被用来表示“填充默认值”,那么就没有办法明确地创建一个无操作复制构造函数等。

It's again a matter of consistency. The empty body means "do nothing" but for things like copy constructors you really don't want "do nothing" but rather "do all the things you'd normally do if not suppressed." Hence =default. It's necessaryfor overcoming suppressed compiler-generated member functions like copy/move constructors and assignment operators. It's then just "obvious" to make it work for the default constructor as well.

这又是一个一致性问题。空体的意思是“什么都不做”,但对于像复制构造函数这样的事情,你真的不想要“什么都不做”,而是“做所有你通常会做的事情,如果不被压制”。因此=default。克服抑制编译器生成的成员函数(如复制/移动构造函数和赋值运算符)是必要的。然后让它也适用于默认构造函数是“显而易见的”。

It might have been nice to make default constructor with empty bodies and trivial member/base constructors also be considered trivial just as they would have been with =defaultif only to make older code more optimal in some cases, but most low-level code relying on trivial default constructors for optimizations also relies on trivial copy constructors. If you're going to have to go and "fix" all your old copy constructors, it's really not much of a stretch to have to fix all your old default constructors, either. It's also much clearer and more obvious using an explicit =defaultto denote your intentions.

使用空主体制作默认构造函数可能会很好,并且平凡的成员/基构造函数也被认为是平凡的,就像它们=default在某些情况下只是为了使旧代码更优化一样,但大多数低级代码依赖于平凡用于优化的默认构造函数也依赖于平凡的复制构造函数。如果您将不得不去“修复”所有旧的复制构造函数,那么修复所有旧的默认构造函数也不是一件容易的事。使用显式=default来表示您的意图也更清晰、更明显。

There are a few other things that compiler-generated member functions will do that you'd have to explicitly make changes to support, as well. Supporting constexprfor default constructors is one example. It's just easier mentally to use =defaultthan having to mark up functions with all the other special keywords and such that are implied by =defaultand that was one of the themes of C++11: make the language easier. It's still got plenty of warts and back-compat compromises but it's clear that it's a big step forward from C++03 when it comes to ease-of-use.

还有一些编译器生成的成员函数会做的事情,你也必须显式地进行更改以支持。支持constexpr默认构造函数就是一个例子。在精神上使用=default比必须用所有其他特殊关键字标记函数更容易使用,=default而这正是 C++11 的主题之一:使语言更容易。它仍然有很多缺陷和向后兼容的妥协,但很明显,在易用性方面,它比 C++03 向前迈出了一大步。

回答by AnqurVanillapy

Due to the deprecation of std::is_podand its alternative std::is_trivial && std::is_standard_layout, the snippet from @JosephMansfield 's answer becomes:

由于std::is_pod和它的替代品的弃用,std::is_trivial && std::is_standard_layout@JosephMansfield 的答案的片段变为:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() {}
};

int main() {
    static_assert(std::is_trivial_v<X>, "X should be trivial");
    static_assert(std::is_standard_layout_v<X>, "X should be standard layout");

    static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
    static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}

Note that the Yis still of standard layout.

请注意,Y仍然是标准布局。