消除默认/删除移动/复制语义中涉及的样板的好方法是什么
What is a good way to eliminate the boilerplate involved in defaulting/deleting move/copy semantics?
斯科特·迈耶斯(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;
};
这是值得商榷的,就像大多数基于风格的问题一样,但我发现前者的好处:
- 宏是可搜索的,因此您可以找到以不同方式利用移动/复制语义的类,而无需使用复杂的正则表达式。
- 通过C++11不可用的逻辑自然地处理旧编译器上的移动/复制语义
#ifdef
(例如CLASS_NON_MOVABLE
将是无操作的(。
一 - 眼就能看出全班同学在做什么。
- 而且更容易打字:-(
鉴于我在这里做了很多宏观魔术,我的灵魂有点痛苦,我觉得有必要"SO帖子,看看我是否有效地这样做并且不会发疯"。
所以我有几个紧密耦合的问题:
- 我是否以理想有效的方式消除样板?
- 是否有更好/其他或更推荐的方法来实现此目的。我什至愿意接受类似于 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
};
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- 在 c++ 中拥有一组结构的正确方法是什么?
- 通过JNI传递数据数组的最快方法是什么
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 当无法使用模板和宏时,生成类型变体C++代码的最简单方法是什么?
- 在另一个类视图中添加最多2个图表的正确方法是什么
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- 在 c++ 中对类中的 c 字符串动态数组进行排序的最佳方法是什么?
- 在C++中包含原型文件的正确方法是什么?
- 在 OpenCV C++ 中估计基本矩阵之前对相应点进行归一化的正确方法是什么?
- 在PostgreSQL中根据它们的ID选择大量行的最快方法是什么?
- 在OSX上使用CMake将Adobe的XMP工具包构建为共享库的最简单方法是什么?
- 将一系列整数放入类的最佳方法是什么?
- 从长整整转换为uint64_t的推荐方法是什么?
- C++:使用方法调用析构函数的顺序是什么?
- 将此布尔值传递给此函数的最有效方法是什么?