【问题标题】:Most concise way to disable copy and move semantics禁用复制和移动语义的最简洁方法
【发布时间】:2018-07-29 14:49:45
【问题描述】:

以下方法确实有效,但非常乏味:

T(const T&) = delete;
T(T&&) = delete;
T& operator=(const T&) = delete;
T& operator=(T&&) = delete;

我正在尝试发现最简洁的方法。以下是否可行?

T& operator=(T) = delete;

更新

请注意,我选择T& operator=(T) 而不是T& operator=(const T&)T& operator=(T&&),因为它可以同时满足这两个目的。

【问题讨论】:

  • 没有。使用 4 行版本显式禁用所有 4。
  • 基类呢?
  • 似乎在标准中最好删除复制构造函数和复制赋值运算符。比较this discussion
  • boost::noncopyable 是一种简洁而富有表现力的方法,如果您不介意为依赖项付费(或已经拥有它)
  • @ricab 从 C++11 开始我就有点厌烦 Boost,因为它承诺向后兼容 C++03。

标签: c++ c++11 copy move-semantics


【解决方案1】:

根据这张图表(由 Howard Hinnant 提供):

简洁的方式是=delete移动赋值运算符(或者移动构造函数,但是会导致cmets中提到的问题)。

不过,在我看来,最可读的方式是=deleteboth复制构造函数和复制赋值运算符。

【讨论】:

  • 我还建议只使用赋值运算符,而不是移动 c'tor。显式删除 c'tor 会弄乱 RVO
  • @Lingxi - 不会影响其他特殊成员函数的生成。例如,您仍然会创建一个复制/移动 c'tor。
  • 注意红细胞;这些是标准设计中的缺陷和问题。
  • 我的指导方针是never delete the move members.
【解决方案2】:

你可以写一个简单的struct 并继承它:

struct crippled
{
    crippled() = default;

    crippled(const crippled&) = delete;
    crippled(crippled&&) = delete;

    crippled& operator=(const crippled&) = delete;
    crippled& operator=(crippled&&) = delete;
};

用法:

struct my_class : crippled
{

};

int main()
{
    my_class a;
    auto b = a; // fails to compile
}

【讨论】:

【解决方案3】:

我更喜欢从 boost::noncopyable 继承,从而立即明确意图并将细节委托给值得信赖的库。

#include <boost/core/noncopyable.hpp>

class X: private boost::noncopyable
{
};

它涉及添加一个依赖项,但如果您对此感到满意,那么它可以说是一种非常简洁和富有表现力的方式来完成它。

【讨论】:

  • 查看我对您答案的变化。
【解决方案4】:

请不要试图寻找“最简洁的方式”来编写一段代码。

如果没有一种明显的表达方式,但也非常简洁 - 不要试图通过语言律师来减少写几个字符的方式。为什么?想想人们阅读你的代码:如果你需要查阅标准来实现你的代码做你想做的事——那么你的代码的读者也会这样做。除了他们不知道你想要达到什么目标;所以他们不会参考标准;所以他们只会对你的代码做了什么感到困惑。或者 - 有些人会得到它,有些人不会。*

在你的情况下,如果你做了这些删除的一个子集,或者使用了一些其他“聪明”的技巧 - 作为一个阅读你的代码的人,我很可能不会明白,没有注意到你实际上是想得到删除所有复制和移动语义。而且我会感到困惑,以为您正在尝试做其他事情。事实上,如果我是你,我什至会考虑添加一条评论:

/* Disabling copy and move semantics because XYZ */
T(const T&) = delete;
T(T&&) = delete;
T& operator=(const T&) = delete;
T& operator=(T&&) = delete;

这更“乏味”,但会让你的未来读者非常清楚你的意图/动机。

还有一个问题是“XYZ”的确切原因是什么。有些人会争辩说,删除移动成员没有充分的理由,而且这样做通常是个坏主意。 C++ 杰出人物 Howard Hinnant 有 this to say 讨论此事。

* - 我阐明的原则的变体here

【讨论】:

  • 您假设最简洁的方式表达力较差,但情况不一定如此。如果能在保持精确的情况下简洁地表达意图,则意图会更加清晰。
  • @ricab:见编辑。关键是“最简洁”不是一种美德。
  • 我坚持认为,如果它不违背表现力的话。 boost::noncopyable 是实现 IMO 的一个很好的例子
  • @ricab 但事实并非如此。我仍然必须仔细检查boost::noncopyable 是否仅禁用复制或是否也禁用移动。移动值不是复制。
  • @Kaihaku,你需要知道代码中使用的构建块是做什么的。情况总是如此,而且比每次都重写代码要好。寻找noncopyable 本质上只是DRY 的一个例子。这个名字可能会更好,但你知道,历史......
【解决方案5】:

我相信在这种情况下,宏实际上更具可读性:

#define NOT_COPYABLE( TypeName ) \
TypeName ( TypeName const& ) = delete; \
TypeName & operator = ( TypeName const& ) = delete;

#define NOT_MOVEABLE( TypeName ) \
TypeName ( TypeName && ) = delete; \
TypeName & operator = ( TypeName && ) = delete;

【讨论】:

    【解决方案6】:

    @ricab's answer 建议不要发明轮子,并使用boost::noncopyable 作为mix-in 基类。这确实有效,但缺点是令人困惑! ...不可复制并不意味着一个类是不可移动的。人们必须记住这个boost类命名的历史背景;大多数人肯定不会,我也不会。

    好吧,你也可以这样做,但重命名这个类:

    #include <boost/core/noncopyable.hpp>
    
    namespace mixins {
    using noncopyable_and_nonmovable = boost::noncopyable;
    } // namespace mixins 
    
    class X: private mixins::noncopyable_and_nonmovable {
      // etc. etc.
    };
    

    现在你所做的事情已经很清楚了:-)

    此外,您可能需要考虑使用此定义和可能的其他定义(例如,仅不可复制、仅不可移动等)在某些实用程序标头中创建一个小的 mixin 命名空间。这将使该解决方案的使用更加简洁。


    我仍然会考虑解释禁止复制和移动某处的决定的原因;见我的other answer

    【讨论】:

    • 好吧,这可能是个好主意。我不知道“大多数人”,但肯定是“某些人”,如果这对他们有帮助就足够了。特别是因为boost::noncopyable 的文档对移动保持沉默。根据语言规则,它们被删除(参见投票最多的答案),但最好在名称中明确说明。
    • @ricab:可以肯定的是,大多数使用图书馆的人都不知道图书馆的发展历史。以及关于像 Boost 这样具有悠久历史的大型库的更安全的假设。我绝对不会猜到boost::noncopyable 是不可移动的,我认为自己并非完全一无所知。
    • 历史可以解释这个名字,但是不可复制的文档说复制操作被删除了。因此不会产生任何移动操作。我完全赞成将名称作为文档的第一个来源,但它们仍然不是唯一的。无论如何,你已经得到了我的支持。
    • @ricab:嗯,唯一指针删除了复制ctor,而没有删除移动运算符。因此,不可复制的类是不可移动的,这并不是一个明显的假设。而且我们不能指望人们仔细检查 mixin 类的来源。
    猜你喜欢
    • 2014-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 2013-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多