"= default"是否允许离线实施?

Does "= default" allow out-of-line implementations?

本文关键字:离线 default 是否 许离线      更新时间:2023-10-16

通常我在标头中看到使用的= default语法。我的理解是,这与函数在标头中显式实现的情况相同,请参见下面的Foo

Foo.h

#pragma once
class Foo
{
public:
    Foo() = default;
    Foo(const Foo& other) = default;
};

纯粹出于好奇,= default可以在源文件中使用如下吗?

Bar.h

#pragma once
class Bar
{
public:
    Bar();
    Bar(const Bar& other);
};

Bar.cpp

#include "Bar.h"
Bar::Bar() = default;
Bar::Bar(const Bar&) = default;

据我所知,这相当于显式地实现cpp文件中的函数。

上面的Bar示例与gcc-5.1一起编译,但标准允许这种用法吗?

顺便说一句,在源文件中使用= default与标头相比有什么好处吗?

是的,这是合法的。从[dcl.fct.def.default]

显式默认函数和隐式声明函数统称为默认函数,实现应为它们提供隐式定义(12.1、12.4、12.8),这可能意味着将它们定义为已删除如果函数是用户声明的,并且在第一次声明时没有显式默认或删除,那么它就是用户提供的。用户提供的显式默认函数(即,在其第一次声明后显式默认)是在显式默认的点定义的如果这样的函数被隐式定义为已删除,则表示程序格式不正确。

强调矿

然后他们会详细介绍的具体情况

struct nontrivial1 {
  nontrivial1();
};
nontrivial1::nontrivial1() = default; // not first declaration

因此,只要函数没有被隐式标记为已删除,那么函数就会被定义为显式默认的函数

顺便说一句,在源文件中使用= default与头文件相比有什么好处吗?

我能看到的唯一"优势"是,它允许现有的代码库更改其cpp文件,以使用现代技术,而不必更改头文件。标准中甚至有一条注释:

注意:在函数的第一次声明后将其声明为默认值,可以提供高效的执行和简洁的定义,同时为不断发展的代码库提供稳定的二进制接口。

在源文件中默认而不是在头文件中默认的一个潜在用途是在unique_ptr中使用pimpl习惯用法。它需要一个完整的类型来构造和销毁,所以不能在头中定义那些特殊的成员。你必须做:

Foo.h

struct Foo { 
    struct Impl;
    unique_ptr<Impl> p;
    Foo();
    ~Foo();
};

Foo.cpp

// Foo::Impl definition here
// now Impl isn't incomplete
Foo::Foo() = default;
Foo::~Foo() = default;

是的,特殊成员函数可能默认为"越界";编译器将生成正确的代码,它将按预期工作。

事实上,有一条规则是关于特殊成员在第一次声明时不被默认的,然后它们被认为是用户提供的(因此不是微不足道的)。

如果函数是用户声明的,并且在第一次声明时没有显式默认或删除,则该函数是用户提供的。用户提供的显式默认函数(即,在其第一次声明后显式默认)是在显式默认的点定义的;如果这样的函数被隐式定义为已删除,那么程序就是格式错误的。

链接到这里[dcl.fct.def.default]。下面的例子详细说明了你的情况;

struct nontrivial1 {
  nontrivial1();
};
nontrivial1::nontrivial1() = default; // not first declaration

它的有用性在于它所做的事情,它提供了默认的实现,但不是在声明时,因此使它成为用户提供的。如前所述,这在处理尚未完成的类型时非常有用,例如在使用皮条客习语时。它还可以用于将您的类型标记为非平凡类型,从而禁止在需要平凡类型的代码中使用它(例如std::is_trivial)。

行为发生了微小变化。Bar.cpp以外的其他TU看不到它们是默认的,因为它们只看到标头。因此,在cpp中放置默认值将使您的类不可平凡地赋值,也不可平凡的构造。

在某些情况下,您需要这样做:如果您的类将unique_ptr保存为不完整类型,那么在cpp中默认析构函数是一种很好的做法,因为如果您不这样做,则使用您的类的类将需要不完整类型的析构函数可见。

在源文件中实现时,方法不再是默认,而是用户提供