移动构造函数的静态断言与复制构造函数不同

时间:2017-02-08 13:55:28

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

想象一下,我有一个类A,移动成本低,复制成本高。它可能看起来像

class A {
  public: 
    [...]

  private:
    HeavyClass m;
};

对于这个课程,我想要一个静态验证该课程 (1)移动可构造和 (2)不仅仅使用复制构造函数进行移动构造, 与是否明确声明移动构造函数无关。

这可能吗?

至于为什么我想这样做,请考虑以下示例:首先,该类自动生成移动构造函数,并根据需要运行。然后,有人更改了类并添加了析构函数,这导致不会隐式生成移动构造函数,而是使用复制构造函数。

因此,static_assert是理想的,但似乎is_move_constructibleis_trivially_move_constructible都不适用于此。

另外,我知道所有这些类都可以A(A&&) = default;,但是static_assert的解决方案会更清晰,并允许在类定义之外进行检查(在其他类中为fi)依赖这个课程的项目。)

修改
我不想禁止复制构造,我只想确保移动构造函数不使用它...

3 个答案:

答案 0 :(得分:4)

如果您可以更改A以获得间接,则可以执行以下操作:

template <bool>
struct MoveOnly {
    MoveOnly() = default;
    ~MoveOnly() = default;
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator=(const MoveOnly&) = delete;
    MoveOnly& operator=(MoveOnly&&) = default;
};

template <> struct MoveOnly<false> {};

template <bool check = false>
class A_Impl : MoveOnly<check> {
public: 
    // ... as ~A_Impl() {}
    // ...
private:
    HeavyClass m;
};

using A = A_Impl<false>; // Normal case

// The check
static_assert(std::is_move_constructible<A_Impl<true>>::value, "");

Demo

答案 1 :(得分:0)

声明析构函数时,复制构造函数和复制赋值运算符的生成为deprecated。不需要静态断言或模板,这是现代C ++的一部分。

解决方案只是启用弃用警告

如果您还没有将警告变成错误,也会将其变为错误。

这样您就不必记得全部添加静态断言。此外,只要您不复制或移动此类对象的任何实例,您仍然可以添加析构函数,具有不可移动的成员并继承自不可移动的。当他们不适用时,没有任何限制。

使用此设置,您可以尝试将此代码添加到您的课程中(典型的回归)

virtual ~A() = default;

如果您尝试移动立即复制A的实例,则编译将失败。

来自 clang-3.9 -Werror -Wdeprecated

的示例错误消息
main.cpp:13:13: error: definition of implicit copy assignment
operator for 'A' is deprecated because it has a user-declared 
destructor [-Werror,-Wdeprecated]
    virtual ~A() = default;
            ^
main.cpp:21:7: note: implicit copy assignment operator for
'A' first required here
    b = std::move(a);
      ^
1 error generated.

如果您创建了A的实例并且只是通过const reference传递它,那么您的编译器就不会抱怨。

答案 2 :(得分:0)

这是一个完全通用的维护问题。理想情况下,我们希望找到一个通用的解决方案。 手动添加断言是一个糟糕的解决方案。它容易出错,容易忘记,耗时并且使代码的可读性降低。

一个通用解决方案是使用静态分析器为所有类强制执行Rule of Zero

Link to SO question about this。但是,如果编译器支持它,则添加编译器选项as described in my other answer是更好的解决方案。