无复制构造函数的简单编译时检查

时间:2018-10-15 10:50:05

标签: c++ c++11

创建无复制类型的一个众所周知的习惯用法是创建基类

struct NoCopy {
   NoCopy(){}
   NoCopy(const NoCopy&) = delete;
   NoCopy& operator=(const NoCopy&) = delete;  
};

从中得出,就像这样

struct Foo : NoCopy {
    Foo(){}
};

这会使以下内容无法编译

Foo f;
Foo f2 = f;

但是我该如何执行呢?任何派生类都可以执行以下操作

struct Foo2 : NoCopy {
    Foo2(){}
    Foo2(const Foo2&){}
};

这是完全合法的,但没有任何意义,我现在有一个既可复制又不可复制的类型(通过其基类)​​。

如何避免这种情况?

3 个答案:

答案 0 :(得分:8)

这是C ++。在模板元编程的世界中,几乎一切皆有可能。如果我们将NoCopy设置为CRTP基础,则可以在其析构函数中添加静态断言。

template<class C>
struct NoCopy {
   NoCopy(){}
   NoCopy(const NoCopy&) = delete;
   NoCopy& operator=(const NoCopy&) = delete;
   ~NoCopy() noexcept {
       static_assert(std::is_base_of<NoCopy, C>::value, "CRTP not observed");
       static_assert(!std::is_copy_constructible<C>::value, "A non-copyable copyable class? Really?");
   }
};

这是您的代码,适用于live example

但这并不是没有代价的,因为现在该类不是微不足道的可破坏的,因此,从该类派生的任何类都不是。能否接受取决于您自己。


进一步考虑,如果您仅提供一种初始化类的方法,则将引用和调用默认构造函数 。因此可以将静态断言移到那里,并且类型又可以被轻易破坏:

template<class C>
struct NoCopy {
   NoCopy() noexcept {
       static_assert(std::is_base_of<NoCopy, C>::value, "CRTP not observed");
       static_assert(!std::is_copy_constructible<C>::value, "A non-copyable copyable class? Really?");
   }
   NoCopy(const NoCopy&) = delete;
   NoCopy& operator=(const NoCopy&) = delete;
};  

静态声明触发的方式与this live example相同。

答案 1 :(得分:3)

如果您的目的是防止人们意外忘记忘记在自己的(禁止命名)副本构造函数中调用NoCopy副本构造函数,我建议这样做:

namespace
{
    struct NotCopyableInitT {};
}    

// You can choose whatever stern words you want here.
NotCopyableInitT initNoCopy() { return {}; }

struct NoCopy {
    explicit NoCopy(NotCopyableInitT){}
    NoCopy(const NoCopy&) = delete;
    NoCopy& operator=(const NoCopy&) = delete;  
};

如果他们坚持在禁止的地方增加可复制性,那么您将迫使他们说出自己的错误:

struct Foo2 : mylib::NoCopy {
    Foo2() : NoCopy(mylib::initNoCopy()) {}
    // Users have to spell out this line in order to get a copy constructor.
    // That certainly goes beyond being forgetful.
    Foo2(const Foo2&) : NoCopy(mylib::initNoCopy()) {}
};

Demo

对于行为良好的用户,这是NoCopy构造函数中的一个附加函数调用(至少一个linter会告诉您无论如何都要显式调用)。

答案 2 :(得分:1)

您可以简单地检查与您的API一起使用的类型不可复制:

#include <type_traits>

namespace lib {
template<class NoCopy>
inline constexpr bool copiable_v = std::disjunction<
    std::is_copy_constructible<NoCopy>,
    std::is_copy_assignable<NoCopy>
>::value;

template<class NoCopy>
struct CheckNoCopiable
{
    static_assert(
        copiable_v<NoCopy> == false,
        "Type is copy-assignable or copy-constructible."
    );
};
}

使用该工具,您的功能可能类似于:

namespace lib {
template<class NoCopy>
void f(NoCopy&& nc)
{
    CheckNoCopiable<NoCopy>{};

    /* do whatever with nc */
}
}

完整程序演示:http://coliru.stacked-crooked.com/a/ed0a8f5576a68554

struct Alice   : lib::NoCopy {}; // nice Alice
struct Bob                   {}; // nice Bob
struct Charlie : lib::NoCopy     // naughty Charly
{
    Charlie()               {};
    Charlie(Charlie const&) {};
    Charlie& operator=(const Charlie&) { return *this; }; 
};

int main()
{
   lib::f(Alice{});
   //lib::f(Bob{});     // error: static assertion failed: Type is copy-assignable or copy-constructible.
   //lib::f(Charlie{}); // error: static assertion failed: Type is copy-assignable or copy-constructible.
}