C++核心准则​讨论:保持定义复制,移动和析构函数的一致性

2020-12-15 15:04:31 浏览数 (2)

Discussion: Define Copy, move, and destroy consistently

讨论:保持定义复制,移动和析构函数的一致性

Reason(原因)

???

Note(注意)

If you define a copy constructor, you must also define a copy assignment operator.

如果定义了拷贝构造函数,则还必须定义一个拷贝赋值运算符。

Note(注意)

If you define a move constructor, you must also define a move assignment operator.

如果定义了移动构造函数,则还必须定义一个移动赋值运算符。

Example(示例)

代码语言:javascript复制
class X {
public:
    X(const X&) { /* stuff */ }

    // BAD: failed to also define a copy assignment operator

    X(x&&) noexcept { /* stuff */ }

    // BAD: failed to also define a move assignment operator

    // ...
};

X x1;
X x2 = x1; // ok
x2 = x1;   // pitfall: either fails to compile, or does something suspicious

If you define a destructor, you should not use the compiler-generated copy or move operation; you probably need to define or suppress copy and/or move.

如果定义了析构函数,则不应使用编译器生成的复制或移动操作。您可能需要定义或抑制复制和/或移动操作。

代码语言:javascript复制
class X {
    HANDLE hnd;
    // ...
public:
    ~X() { /* custom stuff, such as closing hnd */ }
    // suspicious: no mention of copying or moving -- what happens to hnd?
};

X x1;
X x2 = x1; // pitfall: either fails to compile, or does something suspicious
x2 = x1;   // pitfall: either fails to compile, or does something suspicious

If you define copying, and any base or member has a type that defines a move operation, you should also define a move operation.

如果您在定义拷贝操作,如果任何基类或成员的类型具有移动操作,则还应该定义移动操作。

代码语言:javascript复制
class X {
    string s; // defines more efficient move operations
    // ... other data members ...
public:
    X(const X&) { /* stuff */ }
    X& operator=(const X&) { /* stuff */ }

    // BAD: failed to also define a move construction and move assignment
    // (why wasn't the custom "stuff" repeated here?)
};

X test()
{
    X local;
    // ...
    return local;  // pitfall: will be inefficient and/or do the wrong thing
}

If you define any of the copy constructor, copy assignment operator, or destructor, you probably should define the others.

如果定义了拷贝构造函数,拷贝赋值运算符或析构函数中的任何一个,则可能应该定义其他所有函数。

Note(注意)

If you need to define any of these five functions, it means you need it to do more than its default behavior -- and the five are asymmetrically interrelated. Here's how:

如果您需要定义这五个函数中的任何一个,则意味着您需要它做更多的工作而不是其默认行为-并且这五个函数是不对称地相互关联的。就是这样:

  • If you write/disable either of the copy constructor or the copy assignment operator, you probably need to do the same for the other: If one does "special" work, probably so should the other because the two functions should have similar effects. (See Item 53, which expands on this point in isolation.) 如果您编写/禁用了拷贝构造函数或拷贝赋值运算符,则可能需要对另一个函数进行相同的操作:如果一个函数执行“特殊”工作,那么另一个函数也应该这样做,因为这两个函数应具有相似的效果。(请参阅第53项,它在这一点上单独展开。)
  • If you explicitly write the copying functions, you probably need to write the destructor: If the "special" work in the copy constructor is to allocate or duplicate some resource (e.g., memory, file, socket), you need to deallocate it in the destructor. 如果您显式地编写拷贝函数,则可能需要编写析构函数:如果拷贝构造函数中的“特殊”工作是分配或拷贝某些资源(例如,内存,文件,套接字),则需要在析构函数中释放它。
  • If you explicitly write the destructor, you probably need to explicitly write or disable copying: If you have to write a non-trivial destructor, it's often because you need to manually release a resource that the object held. If so, it is likely that those resources require careful duplication, and then you need to pay attention to the way objects are copied and assigned, or disable copying completely. 如果显式地编写了析构函数,则可能需要显式地编写或禁用拷贝操作:如果必须编写非平凡的析构函数,通常是因为您需要手动释放对象持有的资源。如果是这样,则这些资源可能需要仔细复制,然后您需要注意复制和分配对象的方式,或者完全禁用复制。

In many cases, holding properly encapsulated resources using RAII "owning" objects can eliminate the need to write these operations yourself. (See Item 13.)

在许多情况下,使用RAII“拥有”对象保存正确封装的资源可以消除自己编写这些操作的需要。(请参阅第13项。)

Prefer compiler-generated (including =default) special members; only these can be classified as "trivial", and at least one major standard library vendor heavily optimizes for classes having trivial special members. This is likely to become common practice.

首选编译器生成的(包括= default)特殊成员;只有这些可以归类为“琐碎的”,并且至少一个主要的标准库供应商针对具有琐碎的特殊成员的类进行了重度优化。这很可能会成为惯例。

Exceptions: When any of the special functions are declared only to make them non-public or virtual, but without special semantics, it doesn't imply that the others are needed. In rare cases, classes that have members of strange types (such as reference members) are an exception because they have peculiar copy semantics. In a class holding a reference, you likely need to write the copy constructor and the assignment operator, but the default destructor already does the right thing. (Note that using a reference member is almost always wrong.)

例外:当声明任何特殊函数只是为了使它们成为非公共或虚拟的,而没有特殊语义时,并不意味着需要其他特殊功能。在极少数情况下,具有奇怪类型的成员(例如引用成员)的类是例外,因为它们具有特殊的复制语义。在一个包含引用的类中,您可能需要编写拷贝构造函数和赋值运算符,但是默认的析构函数已经可以正确处理。(请注意,使用引用成员几乎总是错误的。)

Resource management rule summary:

资源管理规则摘要:

  • Provide strong resource safety; that is, never leak anything that you think of as a resource 提供强大的资源安全性;也就是说,永远不要泄漏任何您认为是资源的东西
  • Never throw while holding a resource not owned by a handle 持有没有被句柄拥有的资源时切勿抛出异常
  • A "raw" pointer or reference is never a resource handle “原始”指针或引用绝不是资源句柄
  • Never let a pointer outlive the object it points to 永远不要让指针值的生命周期超过它所指向的对象
  • Use templates to express containers (and other resource handles) 使用模板来表达容器(和其他资源句柄)
  • Return containers by value (relying on move or copy elision for efficiency) 按值返回容器(依靠移动或复制省略以提高效率)
  • If a class is a resource handle, it needs a constructor, a destructor, and copy and/or move operations 如果类是资源句柄,则它需要一个构造函数,一个析构函数以及复制和/或移动操作
  • If a class is a container, give it an initializer-list constructor 如果类是容器,请为其提供一个初始化器列表形式的构造函数

原文链接https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#define-copy-move-and-destroy-consistently

0 人点赞