昨晚我无法入睡,开始思考 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的特化。请注意,这不适用于类模板,因为您无法部分特化函数模板,并且在std命名空间中重载名称是非法的。解决方案是在自己的命名空间中编写模板函数,并依赖于参数相关查找来找到它。这在很大程度上取决于客户端遵循“使用std :: swap惯用语”而不是直接调用std :: swap。非常脆弱。
在C++0x中,如果Foo具有用户定义的移动构造函数和移动分配运算符,则提供自定义swap方法和std :: swap 特化几乎没有性能优势,因为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
的现有用例都可以使用移动语义更好的解决方案。 - ascheplerswap(*polymorphicPtr1,*polymorphicPtr2)
... 你的交换函数将会交换两个对象的 vtable... 如果有人在调用 swap 后调用虚函数,这将会造成混乱。 - smerlinstd::is_polymorphic
类型特性。 - fredoverflow