条款25:考虑写出一个不抛异常的swap函数(Consider support for a non-throwing swap)
条款25:考虑写出一个不抛异常的swap函数
C++11之前,std::swap是通过标准的交换算法完成的(C++11之后用的是std::move,并明确声明为noexcept)。它的典型实现正是你所期望的:
namespace std {
template<typename T> // typical implementation of std::swap;
void swap(T& a, T& b) // swaps a's and b's values
{
T temp(a);
a = b;
b = temp;
}
}
它涉及到将三个对象复制:对于某些类型,这些复制是不必要的。特别是主要由一个指针组成,指向另一种包含实际数据的类型。这种设计方法的常见表现形式是“pimpl(pointer to implementation)方法”(见条款31)。
class WidgetImpl { // 负责Widget数据的类,细节不重要
public:
...
private:
int a, b, c;// 可能会有很多数据
std::vector<double> v; // 复制的话,时间会很长!
...
};
class Widget { // 使用pimpl方法的类
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs) // 复制Widget时, 复制
{ // 它的WidgetImpl 对象.
... //
*pImpl = *(rhs.pImpl); // operator= 的常规实现
... //
}
//要交换两个Widget对象的值,真正需要做的就是交换它们的pImpl指针,但默认的交换算法无法知道这一点。
...
private:
WidgetImpl* pImpl; // 指针,指向的对象包含Widget数据
};
为Widget特化std::swap。这是它的基本思想,尽管它不能以这种形式编译:
namespace std {
template<> // 这是一个特化版本
void swap<Widget>(Widget& a, Widget& b) // 当T是Widget时的std::swap;这无法编译
{
//它试图访问a和b中的pImpl指针,它们是私有的,所以无法通过编译!
swap(a.pImpl, b.pImpl); // 要交换Widget,只需交换它们的pImpl指针
}
}
要让Widget声明一个名为swap的公共成员函数来进行实际的交换,然后使用特化的std::swap来调用该成员函数:
class Widget { // 只是增加了swap成员函数
public:
...
void swap(Widget& other)
{
using std::swap;
swap(pImpl, other.pImpl); // 交换Widget,只需交换它们的pImpl指针
}
...
};
namespace std {
template<> // 修订后的特化版std::swap
void swap<Widget>(Widget& a,
Widget& b)
{
a.swap(b); // 交换Widget, 代用它们的swap成员函数
}
}
然而,假设Widget和WidgetImpl是类模板而不是类,用于参数化WidgetImpl中的数据类型:
template<typename T>
class WidgetImpl { ... };
template<typename T>
class Widget { ... };
namespace std {
template<typename T>
//C++允许类模板的偏特化,但不允许函数模板的偏特化。这段代码不应该编译通过(尽管有些编译器会错误地接受它)。
void swap<Widget<T>>(Widget<T>& a, Widget<T>& b)// 错误!
{
a.swap(b);
}
}
当你想“部分特化”一个模板函数时,通常的方法是简单地添加一个重载。
namespace std {
//一般来说,重载函数模板是可以的,但std是一个特殊的命名空间。不可以添加新的模板(或类或函数或其他)。这样的行为是未定义的,虽然大多数情况下可以正常编译
template<typename T> // std::swap的一个重载版本
void swap(Widget<T>& a, Widget<T>& b)
{
a.swap(b);
} // 这也不合法
}
如果所有与Widget相关的功能都在命名空间WidgetStuff中
namespace WidgetStuff {
... // 模板化的WidgetImpl, 等等.
template<typename T> // 和以前一样, 包含swap成员函数
class Widget { ... };
...
template<typename T> // 非成员函数版swap,不是std命名空间的一部分
void swap(Widget<T>& a, Widget<T>& b)
{
a.swap(b);
}
}
template<typename T>
void doSomething(T& obj1, T& obj2)
{
using std::swap; // 使得 std::swap 在函数内有效
...
swap(obj1, obj2); // 调用更高效的T(Widget)的特化版本
...
}
总结:
- 当std::swap对某类型不够高效时,请提供一个交换成员函数。确保它不会抛出异常。
- 如果提供成员函数swap,也要提供调用成员函数的非成员函数swap。对于类(而不是模板),也要特化std::swap。
- 在调用swap函数时,先对std::swap使用using声明,然后再调用swap函数,而不需要限定命名空间。
- 在std命名空间里可以为用户定义类型进行模板函数的全特化,但绝对不要试图向std中添加全新的东西。
原文地址:https://blog.csdn.net/weixin_43739503/article/details/144661937
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!