创建无复制类型的一个众所周知的习惯用法是创建基类
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&){}
};
这是完全合法的,但没有任何意义,我现在有一个既可复制又不可复制的类型(通过其基类)。
如何避免这种情况?
答案 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()) {}
};
对于行为良好的用户,这是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.
}