应该将以`= default`声明的函数仅放在头文件中 [英] Should functions declared with `= default` only go in the header file

查看:57
本文介绍了应该将以`= default`声明的函数仅放在头文件中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在定义类时,现在通常将 = default 用于析构函数/副本构造函数和副本分配.从我的代码库来看,这些几乎总是只存在于头文件中,但是一些同事将它们放在了 .cpp 文件中.在这种情况下,最佳做法是什么?

When defining classes, it is now common to use = default for the destructor/copy constructor and copy assignment. Looking at my code base, these are nearly always in the header files only but some colleagues have put them in the .cpp file. What would be the best practice in this case?

编译器是否在头文件中多次生成这些函数,并依靠链接器对其进行分解.如果您的班级很大,也许只值得将它们放在 .cpp 文件中吗?对于我们大多数较旧的C ++ 98代码,不执行任何操作的功能通常也仅在标头中定义.什么也不做虚拟析构函数似乎经常被移到 .cpp 文件中.对于需要填充虚拟方法表的地址的虚拟方法来说,它是否重要(或曾经)重要.

Is the compiler generating these functions multiple times when it is in the header and relying on the linker to dedup them. Is it perhaps only worth putting them in the .cpp file if you have a huge class? With our mostly old C++98 code, functions that do nothing are also often defined only in the header. Do nothing virtual destructors seem to have often been moved to the .cpp file. Is (or was) it somehow important for virtual methods where their address is needed to populate the virtual method table.

是否还建议将 noexcept()子句放在 = default 函数上?编译器似乎是自身派生的,因此仅在存在的情况下才用作API文档.

Also is it recommended to ever put noexcept() clauses on = default functions? The compiler appears to derive this itself so it only serves as API documentation if it is there.

推荐答案

显式默认的功能不一定是 not 用户提供的

An explicitly-defaulted function is not necessarily not user-provided

在这种情况下,最佳做法是什么?

What would be the best practice in this case?

根据经验,除非您明确且只想知道自己要进入的领域,否则我建议始终定义显式默认函数他们的(第一个)声明;即,在第一个声明中放置 = default ,这意味着(在您的情况下)标头(具体是类定义),因为两者之间存在细微但本质上的区别.是否认为构造函数是用户提供的.

I would recommend, as a rule of thumb, unless you explicitly and wantonly know what you are getting into, to always define explicitly-defaulted functions at their (first) declaration; i.e., placing = default at the (first) declaration, meaning in (your case) the header (specifically, the class definition), as there are subtle but essential differences between the two w.r.t. whether a constructor is considered to be user-provided or not.

来自 [dcl.fct.def.default]/5 [提取,重点我的]:

From [dcl.fct.def.default]/5 [extract, emphasis mine]:

[...]如果函数是由用户声明的,并且未在其第一个声明中明确默认或删除的,则由用户提供. [...]

因此:

struct A {
    A() = default; // NOT user-provided.
    int a;
};


struct B {
    B(); // user-provided.
    int b;
};

// A user-provided explicitly-defaulted constructor.
B::B() = default;

构造器是否由用户提供,反过来影响初始化该类型对象的规则.特别是,当值初始化时,如果 T ,则类类型 T 首先将对对象进行零初始化.code>的默认构造函数不是用户提供的.因此,此保证适用于上面的 A ,但不适用于 B ,并且用(用户提供的!)对对象的值初始化非常令人惊讶.默认构造函数使对象的数据成员处于未初始化状态.

Whether a constructor is user-provided or not does, in turn, affect the rules for which objects of the type are initialized. Particularly, a class type T, when value-initialized, will first zero-initialize the object if T's default constructor is not user-provided. Thus, this guarantee holds for A above, but not for B, and it can be quite surprising that a value-initialization of an object with a (user-provided!) defaulted constructor leaves data members of the object in an uninitialized state.

从cppreference引用 [摘录,重点>我的]:

Quoting from cppreference [extract, emphasis mine]:

值初始化

在以下情况下执行值初始化:

Value initialization

Value initialization is performed in these situations:

  • [...]
  • (4),当使用由一对大括号组成的初始化程序声明一个命名变量(自动,静态或线程局部)时.

值初始化的影响是:

  • (1)如果 T 是没有默认构造函数的类类型,或者是具有用户提供的或已删除的默认构造函数的类类型,对象已默认初始化

  • (1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;

(2),如果 T 是类类型,并且默认构造函数既不是用户提供也不是删除(也就是说,它可能是一个类(使用隐式定义或默认的默认构造函数),将对象初始化为零,然后使用默认构造函数对其进行默认初始化

(2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor;

...

让我们将其应用于上面的 A B 类类型:

Let's apply this on the class types A and B above:

A a{};
// Empty brace direct-list-init:
// -> A has no user-provided constructor
// -> aggregate initialization
// -> data member 'a' is value-initialized
// -> data member 'a' is zero-initialized

B b{};
// Empty brace direct-list-init:
// -> B has a user-provided constructor
// -> value-initialization
// -> default-initialization
// -> the explicitly-defaulted constructor will
//    not initialize the data member 'b'
// -> data member 'b' is left in an unititialized state

a.a = b.b; // reading uninitialized b.b: UB!

因此,即使在您最终不会用脚射击的用例中,也只是在代码库中没有定义明确默认(特殊成员)功能的存在模式在他们的(第一个)声明中,可能会导致 other 开发人员,他们不知不觉地意识到了这种模式的微妙之处,盲目地跟随它,随后开枪自杀.

Thus, even for use cases where you will not end up shooting yourself in the foot, just the presence of a pattern in your code base where explicitly defaulted (special member) functions are not being defined at their (first) declarations may lead to other developers, unknowingly of the subtleties of this pattern, blindly following it and subsequently shooting themselves in their feet instead.

这篇关于应该将以`= default`声明的函数仅放在头文件中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆