【发布时间】:2011-06-20 00:06:22
【问题描述】:
昨晚睡不着,开始想std::swap。这是我们熟悉的 C++98 版本:
template <typename T>
void swap(T& a, T& b)
{
T c(a);
a = b;
b = c;
}
如果用户定义的类Foo 使用外部资源,这是低效的。常见的习惯用法是提供一个方法void Foo::swap(Foo& other) 和一个专门化的std::swap<Foo>。请注意,这不适用于类 templates,因为您不能部分特化函数模板,并且在 std 命名空间中重载名称是非法的。解决方案是在自己的命名空间中编写一个模板函数,并依靠参数依赖查找来找到它。这严重依赖于客户端遵循“using std::swap idiom”而不是直接调用std::swap。很脆。
在 C++0x 中,如果 Foo 具有用户定义的移动构造函数和移动赋值运算符,则提供自定义 swap 方法和 std::swap<Foo> 特化几乎没有性能优势,因为 C+ +0x 版本的std::swap 使用高效的移动而不是复制:
#include <utility>
template <typename T>
void swap(T& a, T& b)
{
T c(std::move(a));
a = std::move(b);
b = std::move(c);
}
不必再摆弄swap 已经为程序员减轻了很多负担。
当前的编译器还没有自动生成移动构造函数和移动赋值运算符,但据我所知,这将改变。剩下的唯一问题是异常安全,因为一般来说,移动操作是允许抛出的,这会打开一大堆蠕虫。问题“移出对象的状态究竟是什么?”让事情变得更加复杂。
然后我在想,如果一切顺利,C++0x 中std::swap 的语义到底是什么?交换前后对象的状态如何?通常,通过移动操作进行交换不会触及外部资源,只会触及“平面”对象表示本身。
那么,为什么不简单地编写一个 swap 模板来做到这一点:交换对象表示?
#include <cstring>
template <typename T>
void swap(T& a, T& b)
{
unsigned char c[sizeof(T)];
memcpy( c, &a, sizeof(T));
memcpy(&a, &b, sizeof(T));
memcpy(&b, c, sizeof(T));
}
这是最有效的:它只是通过原始内存爆炸。它不需要用户的任何干预:不需要定义特殊的交换方法或移动操作。这意味着它甚至可以在 C++98 中工作(请注意,它没有右值引用)。但更重要的是,我们现在可以忘记异常安全问题,因为 memcpy 永远不会抛出异常。
我可以看到这种方法的两个潜在问题:
首先,并非所有对象都需要交换。如果类设计器隐藏了复制构造函数或复制赋值运算符,则尝试交换类的对象应该在编译时失败。我们可以简单地引入一些死代码来检查类型上的复制和赋值是否合法:
template <typename T>
void swap(T& a, T& b)
{
if (false) // dead code, never executed
{
T c(a); // copy-constructible?
a = b; // assignable?
}
unsigned char c[sizeof(T)];
std::memcpy( c, &a, sizeof(T));
std::memcpy(&a, &b, sizeof(T));
std::memcpy(&b, c, sizeof(T));
}
任何体面的编译器都可以轻松摆脱死代码。 (可能有更好的方法来检查“交换一致性”,但这不是重点。重要的是它是可能的)。
其次,某些类型可能会在复制构造函数和复制赋值运算符中执行“不寻常”的操作。例如,他们可能会通知观察者他们的变化。我认为这是一个小问题,因为这类对象可能一开始就不应该提供复制操作。
请告诉我您对这种交换方法的看法。它会在实践中起作用吗?你会用吗?你能确定这会破坏的库类型吗?您是否看到其他问题?讨论!
【问题讨论】:
-
std::swap的大多数当前用例无论如何都会有更好的使用移动语义的解决方案。 -
是移动语义和移动构造函数。看到这个,stackoverflow.com/questions/4820643/…
-
-
@smerlin:但是多态对象真的不应该有复制构造函数或赋值运算符,对吧?
-
@smerlin:实际上,是的 :) C++0x 提供了
std::is_polymorphic类型特征。
标签: c++ swap move-semantics c++11