浏览一些 C++ 问题,我经常看到这样的评论,即对 STL 友好的类应该实现一个swap
函数(通常作为朋友)。有人能解释一下这带来了什么好处,STL 如何适应这一点以及为什么这个函数应该作为一个实现friend
?
7 回答
对于大多数类,默认交换很好,但是,默认交换并非在所有情况下都是最佳的。最常见的例子是使用指向实现习语的类。与默认交换一样,会复制大量内存,如果您是专门的交换,则可以通过仅交换指针来显着加快速度。
如果可能,它不应该是类的朋友,但是它可能需要访问您的类可能不想在类 API 中公开的私有数据(例如,原始指针)。
std::swap() 的标准版本适用于大多数可分配的类型。
void std::swap(T& lhs,T& rhs)
{
T tmp(lhs);
lhs = rhs;
rhs = tmp;
}
但这不是最佳实现,因为它调用了复制构造函数,然后调用了两次赋值运算符。
通过为您的类添加您自己的 std::swap() 版本,您可以实现 swap() 的优化版本。
例如 std::vector。上面定义的默认实现将非常昂贵,因为您需要复制整个数据区域。可能会释放旧数据区域或重新分配数据区域,并为复制的每个项目上的包含类型调用复制构造函数。一个专门的版本有一个非常简单的方法来做 std::swap()
// NOTE this is not real code.
// It is just an example to show how much more effecient swaping a vector could
// be. And how using a temporary for the vector object is not required.
std::swap(std::vector<T>& lhs,std::vector<T>& rhs)
{
std::swap(lhs.data,rhs.data); // swap a pointer to the data area
std::swap(lhs.size,rhs.size); // swap a couple of integers with size info.
std::swap(lhs.resv,rhs.resv);
}
因此,如果您的班级可以优化 swap() 操作,那么您可能应该这样做。否则将使用默认版本。
就个人而言,我喜欢将 swap() 实现为非抛出成员方法。然后提供一个专门的 std::swap() 版本:
class X
{
public:
// As a side Note:
// This is also useful for any non trivial class
// Allows the implementation of the assignment operator
// using the copy swap idiom.
void swap(X& rhs) throw (); // No throw exception guarantee
};
// Should be in the same namespace as X.
// This will allows ADL to find the correct swap when used by objects/functions in
// other namespaces.
void swap(X& lhs,X& rhs)
{
lhs.swap(rhs);
}
如果你想交换(例如)两个向量而不知道它们的实现,你基本上必须做这样的事情:
typedef std::vector<int> vec;
void myswap(vec &a, vec &b) {
vec tmp = a;
a = b;
b = tmp;
}
a
如果并且包含许多元素,这是无效的,b
因为所有这些元素都在a
,b
和之间复制tmp
。
但是,如果交换函数知道并可以访问向量的内部,则可能会有更有效的实现:
void std::swap(vec &a, vec &b) {
// assuming the elements of the vector are actually stored in some memory area
// pointed to by vec::data
void *tmp = a.data;
a.data = b.data;
b.data = tmp;
// ...
}
在这个实现中,只需要复制几个指针,而不是像第一个版本中的所有元素。由于这个实现需要访问向量的内部,它必须是一个友元函数。
我将您的问题解释为基本上三个不同(相关)的问题。
- 为什么 STL 需要交换?
- 为什么要实施专门的交换(iso 依赖于默认值
swap
)? - 为什么要实现成朋友呢?
为什么 STL 需要交换?
STL 友好类需要的原因swap
是swap
在许多 STL 算法中用作原始操作。(例如reverse
,sort
等partition
通常使用 来实现swap
)
为什么要实施专门的交换(iso 依赖于默认值
swap
)?
您的这部分问题已经有很多(好的)答案。基本上,了解类的内部结构通常可以让您编写更优化的swap
函数。
为什么要实现成朋友呢?
STL 算法总是将 swap 作为自由函数调用。因此,它需要作为非成员函数可用才能有用。
而且,由于只有当您可以使用内部结构的知识来编写更有效的交换时,编写自定义交换才是有益的,这意味着您的自由函数将需要访问您的类的内部,因此是朋友。
基本上,它不必是朋友,但如果它不需要成为朋友,通常也没有理由实现自定义交换。
请注意,您应该确保自由函数与您的类位于同一名称空间内,以便 STL 算法可以通过 Koening 查找找到您的自由函数。
交换功能的另一种用途是帮助异常安全代码:http ://www.gotw.ca/gotw/059.htm
效率:
如果您有一个包含指向数据的(智能)指针的类,那么交换指针可能比交换实际数据更快 - 3 个指针副本与 3 个深副本。
如果你使用 'using std::swap' + 一个不合格的 swap 调用(或者只是一个合格的 boost::swap 调用),那么 ADL 将选择自定义交换函数,从而允许编写高效的模板代码。
安全:
指针交换(原始指针,std::auto_ptr 和 std::tr1::shared_ptr)不会抛出,因此可用于实现非抛出交换。非抛出交换使编写提供强异常保证的代码(事务代码)变得更容易。
一般模式是:
class MyClass
{
//other members etc...
void method()
{
MyClass finalState(*this);//copy the current class
finalState.f1();//a series of funcion calls that can modify the internal
finalState.f2();//state of finalState and/or throw.
finalState.f3();
//this only gets call if no exception is thrown - so either the entire function
//completes, or no change is made to the object's state at all.
swap(*this,finalState);
}
};
至于是否应该作为朋友执行;交换通常需要了解实现细节。是使用调用成员函数的非朋友还是使用朋友,这是一个品味问题。
问题:
自定义交换通常比单个分配快 - 但单个分配总是比默认的三个分配交换快。如果你想移动一个对象,不可能以通用的方式知道交换或赋值是否是最好的——C++0x 用移动构造函数解决了这个问题。
要实现赋值运算符:
class C
{
C(C const&);
void swap(C&) throw();
C& operator=(C x) { this->swap(x); return *this; }
};
这是异常安全的,当您通过值传递时,复制是通过复制构造函数完成的,并且当您传递临时值时(通过复制省略),编译器可以优化复制。