考虑以下代码:
#include <cstring>
#include <memory>
namespace mstd {
template <typename T>
void swap(T& lhs, T& rhs) {
char tmp[sizeof(T)];
std::memcpy(tmp, std::addressof(lhs), sizeof(T));
std::memcpy(std::addressof(lhs), std::addressof(rhs), sizeof(T));
std::memcpy(std::addressof(rhs), tmp, sizeof(T));
}
}
使用mstd::swap
通常不安全;只有当std::is_trivially_copyable<T>::value
成立时才会这样。
但是我看不出它怎么会出问题。有谁知道一个真实的例子,使用这个交换将带来一个不正确交换的行为,为什么?
答案 0 :(得分:3)
假设一个类包含一个指针,该指针必须指向其自己的一个成员。它将需要复制/移动语义来保留该不变量。逐字节复制将忽略这些语义,使其错误地指向不同对象的成员。
对于特定示例,请考虑具有“小字符串优化”的字符串。如果字符串足够小,它包含一个嵌入式缓冲区来代替动态内存;和指向该缓冲区或动态内存的指针。它需要非平凡的复制(和移动)操作来确保指针不被复制,而是指向目标对象的缓冲区:
string(string const & other) :
size(other.size),
data(other.data == other.buffer ? buffer : new char[size])
{
copy(other.data, other.data+size, data);
}
逐字节交换(两个小字符串)将使每个字符串指向另一个缓冲区。如果一个人在彼此之前被摧毁,你最终会得到一个悬垂的指针;更糟糕的是,一个析构函数实现为
~string() {if (data != buffer) delete [] buffer;}
将删除不应该删除的内容,并提供未定义的行为。
答案 1 :(得分:2)
我想我理解正确,所以我会回答这个问题。
如果ctor以及某个类的副本具有某些副作用,则不会通过按位副本来遵守这些副作用。作为一个例子:如果一个对象在通过地址在中央注册表中创建时注册自己,例如因为它是一个回调函子。然后地址将在交换后指向错误的对象。在该示例中,回调持有者将执行错误的回调。
答案 2 :(得分:0)
我心目中的一个例子是一个类,其中一个孩子有一个指向其父母的指针:
#include <memory>
struct Parent;
struct Child{
Parent* parent;
};
struct Parent {
std::unique_ptr<Child> child;
};
void swap(Parent& lhs, Parent& rhs) {
std::unique_ptr<Child> tmp(std::move(lhs.child));
lhs.child=std::move(rhs.child);
rhs.child=std::move(tmp);
//there is no equivalence for those two lines in your example
lhs.child->parent=&lhs;
rhs.child->parent=&rhs;
}
你会遇到这种情况,例如经常在Qt。