通常我会看到标头中使用的= default
语法。我的理解是,这与在标题中明确实现函数的情况相同,请参阅下面的Foo
。
#pragma once
class Foo
{
public:
Foo() = default;
Foo(const Foo& other) = default;
};
纯粹出于好奇,= default
可以在源文件中使用如下吗?
#pragma once
class Bar
{
public:
Bar();
Bar(const Bar& other);
};
#include "Bar.h"
Bar::Bar() = default;
Bar::Bar(const Bar&) = default;
据我所知,这相当于在cpp文件中明确实现了这些函数。
以上Bar
示例使用gcc-5.1
进行编译,但标准是否允许此用法?
作为一个,在源文件中使用= default
与标题相比有什么好处吗?
答案 0 :(得分:4)
是的,这是合法的。来自 [dcl.fct.def.default]
显式默认函数和隐式声明的函数统称为默认函数,实现应为它们提供隐式定义(12.1 12.4,12.8),这可能意味着将它们定义为已删除。 如果函数是用户声明的,并且在第一个声明中未明确默认或删除,则该函数是用户提供的。用户提供的显式默认函数(即,在其第一次声明后显式默认)在明确默认的位置定义; 如果将此函数隐式定义为已删除,则程序格式错误。
强调我的
然后他们继续用
详细说明你的具体情况struct nontrivial1 { nontrivial1(); }; nontrivial1::nontrivial1() = default; // not first declaration
因此,只要该函数未被隐式标记为已删除,那么将定义函数,您可以在其中明确默认该函数。
顺便说一下,在源文件中使用
= default
与标题相比有什么好处吗?
我能看到的唯一“优势”它允许现有代码库更改其cpp文件以使用现代技术而无需更改头文件。标准中甚至还有一个注释:
注意:在第一次声明后将函数声明为默认值可以提供高效的执行和简洁的定义,同时为不断发展的代码库提供稳定的二进制接口。
答案 1 :(得分:4)
源文件而不是标头中默认的一种潜在用法是将pimpl惯用法与unique_ptr
一起使用。它需要一个完整的构造和销毁类型,因此您无法在标题中定义这些特殊成员。你必须这样做:
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;
答案 2 :(得分:2)
是的,特殊会员功能可能会“脱节”;编译器将生成正确的代码,它将按预期工作。
事实上,有一条规则涉及特殊成员在第一次申报时没有违约,然后被视为用户提供(因此非平凡)。
如果函数是用户声明的,并且在第一个声明中未明确默认或删除,则该函数是用户提供的。用户提供的显式默认函数(即,在其第一次声明后显式默认)是在明确默认的位置定义的;如果将这样的函数隐式定义为已删除,则程序格式不正确。
Link here [dcl.fct.def.default]。通过以下示例详细说明您的情况;
struct nontrivial1 { nontrivial1(); }; nontrivial1::nontrivial1() = default; // not first declaration
它的用处在于它的作用,它提供了默认的实现,但不是在声明时,因此使它成为用户提供的。如上所述,这在处理尚未完成的类型时很有用,例如在使用pimpl习语时。它也可用于将您的类型标记为非平凡,因此禁止在需要简单类型的代码中使用它(例如std::is_trivial
)。
答案 3 :(得分:1)
行为发生了微小变化。除Bar.cpp
之外的其他TU无法看到它们是默认的,因为它们只能看到标题。因此,将默认设置放在cpp中会使您的课程无法轻易分配,也不会轻易构建。
在某些情况下,您希望这样做:如果您的班级将unique_ptr
保留为不完整的类型,那么在cpp中默认析构函数是一种很好的做法,因为如果您没有&#39 ; t,使用你的类将要求不完整类型的析构函数可见。
答案 4 :(得分:0)
在源文件中实现时, 这些方法不再是默认,而是用户提供。