消除默认/删除移动/复制语义中涉及的样板的好方法是什么

What is a good way to eliminate the boilerplate involved in defaulting/deleting move/copy semantics?

本文关键字:是什么 方法 默认 删除 移动 语义 复制      更新时间:2023-10-16

斯科特·迈耶斯(Scott Meyers(对零法则有一个很好的观点。基本上,他提倡默认移动/复制分配/构造,无论您是否真的需要它们。基本上,一般的经验法则是避免这些成员的编译器生成,主要是因为它们是混淆的一大来源(我同意这一点(。

所以我在考虑如何将类定义为默认的可移动、可复制或不可移动、不可复制的良好通用实践。我想到了 boost 的boost::noncopyable但我不喜欢为这样的功能目的引入继承的想法。

我唯一能想到的有意义的事情就是诉诸宏。所以我想出了这样的东西:

/// Disable copy construct/assign for the given class T
#define CLASS_NON_COPYABLE(T) 
   T(T const&) = delete; 
   T& operator=(T const&) = delete
/// Disable move construct/assign for the given class T
#define CLASS_NON_MOVABLE(T) 
   T(T&&) = delete; 
   T& operator=(T&&) = delete
/// Disable both copy and move construct/assign for the given class T
#define CLASS_NON_COPYABLE_OR_MOVABLE(T) 
   CLASS_NON_COPYABLE(T); 
   CLASS_NON_MOVABLE(T)
/// Default copy move/assign
#define CLASS_DEFAULT_COPYABLE(T) 
   T(T const&) = default; 
   T& operator=(T const&) = default
/// Default move construct/assign
#define CLASS_DEFAULT_MOVABLE(T) 
   T(T&&) = default; 
   T& operator=(T&&) = default
/// Defaulted versions of both copy and move construct/assign for the given class T
#define CLASS_DEFAULT_COPYABLE_OR_MOVABLE(T) 
   CLASS_DEFAULT_COPYABLE(T); 
   CLASS_DEFAULT_MOVABLE(T)

以及如何使用它们的示例:

class foo
{
public:
    foo() = default;
    virtual ~foo() = default;
    CLASS_NON_COPYABLE(foo);
    CLASS_DEFAULT_MOVABLE(foo);
};
int main()
{
    foo a, b;
    a = b; // FAIL: can't copy; class is "non copyable"
    a = foo(); // OK: class is 'default movable'
}

(现场样本(

对我来说,这看起来比替代方案干净得多:

class foo
{
public:
    foo() = default;
    virtual ~foo() = default;
    foo(foo const&) = delete;
    foo(foo&&) = default;
    foo& operator=(foo const&) = delete;
    foo& operator=(foo&&) = default;
};

这是值得商榷的,就像大多数基于风格的问题一样,但我发现前者的好处:

  1. 宏是可搜索的,因此您可以找到以不同方式利用移动/复制语义的类,而无需使用复杂的正则表达式。
  2. 通过C++11不可用的逻辑自然地处理旧编译器上的移动/复制语义#ifdef(例如 CLASS_NON_MOVABLE将是无操作的(。
  3. 眼就能看出全班同学在做什么。
  4. 而且更容易打字:-(

鉴于我在这里做了很多宏观魔术,我的灵魂有点痛苦,我觉得有必要"SO帖子,看看我是否有效地这样做并且不会发疯"。

所以我有几个紧密耦合的问题:

  1. 我是否以理想有效的方式消除样板?
  2. 是否有更好/其他或更推荐的方法来实现此目的。我什至愿意接受类似于 Boost 提供的优雅继承解决方案。

也许我做错了。也许这整个问题弄巧成拙,我想多了。我也愿意重新定义整个事情,以防万一我在兔子洞里走得太远了。

我笑了笑,对TemplateRex的好答案投了赞成票。 话虽如此,如果你必须声明你的析构函数是虚拟的,那么你不能把所有事情都留给编译器。 但是,通过了解规则(并假设一个符合要求的编译器(,可以最大限度地减少您必须键入的内容和您必须阅读的内容。

对于这个具体的例子,我建议:

class foo
{
public:
    virtual ~foo()        = default;
    foo()                 = default;
    foo(foo&&)            = default;
    foo& operator=(foo&&) = default;
};

笔记:

  • 如果声明任一移动成员,则会隐式删除这两个副本成员。 您可以使用此规则来减少样板。

  • 我建议把你的数据成员放在班级的顶端,然后你的特殊成员紧随其后。 这与许多建议将数据成员放在底部的指南形成鲜明对比,因为它们对读者来说并不重要。 但是,如果读者想知道特殊成员在默认时将执行什么操作,则读者需要查看数据成员。

  • 我建议始终以相同的顺序对声明的特殊成员进行排序。 这有助于(发起的读者(意识到您何时没有声明特殊成员。 我有一个推荐的订单。 但无论顺序如何,都要保持一致。

  • 我建议的顺序是:析构函数、默认构造函数、复制构造函数、复制赋值、移动构造函数、移动赋值。 我喜欢这个顺序,因为我认为析构函数是最重要的特殊成员。 这个函数告诉我很多关于类设计的信息。 我喜欢将我的复制成员组合在一起,并将我的移动成员组合在一起,因为它们通常都是默认的,或者都是删除的。

就此示例的复制成员而言,鉴于上述样式指南,很容易(至少对我来说(看到它们被隐式删除,因此要阅读的内容较少(肮脏的手指和所有这些:-((。

这是一篇简短的论文,其中包含这种类声明风格的更多基本原理。

更新

冒着跑题的风险,foo.cpp 最好包含确认您正确处理特殊成员:

static_assert(std::is_nothrow_destructible<foo>{},
              "foo should be noexcept destructible");
static_assert(std::has_virtual_destructor<foo>{},
              "foo should have a virtual destructor");
static_assert(std::is_nothrow_default_constructible<foo>{},
              "foo should be noexcept default constructible");
static_assert(!std::is_copy_constructible<foo>{},
              "foo should not be copy constructible");
static_assert(!std::is_copy_assignable<foo>{},
              "foo should not be copy assignable");
static_assert(std::is_nothrow_move_constructible<foo>{},
              "foo should be noexcept move constructible");
static_assert(std::is_nothrow_move_assignable<foo>{},
              "foo should be noexcept move assignable");

我在某些地方添加了"nothrow"。 如果适用,请将其删除,或将其添加到更多位置(如果适用(。 如果适用,请改用"微不足道"。 一种尺寸并不适合所有人。

这种在标题中说出你的意图,并确认你所说的是你在源代码中得到的,非常有利于正确的代码。

将这个"样板"隐藏在宏下是有代价的:读者必须查找宏的定义。 如果使用此类宏,请判断宏的收益是否超过其成本。

@HowardHinnant对零法则有更好的建议:

class foo
{
public:
// just keep your grubby fingers off of the keyboard
};