C 箴言:考虑支持不抛异常的swap
2008-02-23 05:40:38来源:互联网 阅读 ()
swap 是个有趣的函数。最早作为 STL 的一部分被引入,后来他成为异常安全编程(exception-safe programming)的支柱和压制自赋值可能性的通用机制。因为 swap 太有用了,所以正确地实现他很重要,但是伴随他的不同寻常的重要性而来的,是一系列不同寻常的复杂性。在本文中,我们就来研究一下这些复杂性究竟是什么样的连同如何对付他们。
交换两个对象的值就是互相把自己的值送给对方。缺省情况下,通过标准的交换算法来实现交换是很成熟的技术。典型的实现完全符合您的预期:
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;
}
}
只要您的类型支持拷贝(通过拷贝构造函数和拷贝赋值运算符),缺省的 swap 实现就能交换您的类型的对象,而无需您做任何特别的支持工作。
可是,缺省的 swap 实现可能不那么酷。他涉及三个对象的拷贝:从 a 到 temp,从 b 到 a,连同从 temp 到 b。对一些类型来说,这些副本全是不必要的。对于这样的类型,缺省的 swap 就似乎让您坐着快车驶入小巷。
这样的类型中最重要的就是那些主要由一个指针组成的类型,那个指针指向包含真正数据的另一种类型。这种设计方法的一种常见的表现形式是 "pimpl idiom"("pointer to implementation")。一个使用了这种设计的 Widget 类可能就像这样:
class WidgetImpl {
// class for Widget data;
public: // details are unimportant
...
private:
int a, b, c; // possibly lots of data -
std::vector<double> v; // expensive to copy!
...
};
class Widget {
// class using the pimpl idiom
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs) // to copy a Widget, copy its
{
// WidgetImpl object. For
... // details on implementing
*pImpl = *(rhs.pImpl); // operator= in general,
... // see Items 10, 11, and 12.
}
...
private:
WidgetImpl *pImpl; // ptr to object with this
}; // Widget’s data
为了交换这两个 Widget 对象的值,我们实际要做的就是交换他们的 pImpl 指针,但是缺省的交换算法没有办法知道这些。他不但要拷贝三个 Widgets,而且更有三个 WidgetImpl 对象,效率太低了。一点都不酷。
当交换 Widgets 的是时候,我们应该告诉 std::swap 我们打算做什么,执行交换的方法就是交换他们内部的 pImpl 指针。这种方法的正规说法是:针对 Widget 特化 std::swap(specialize std::swap for Widget)。下面是个基本的想法,虽然在这种形式下他还不能通过编译:
namespace std {
template<> // this is a specialized version
void swap<Widget>(Widget& a, // of std::swap for when T is
Widget& b) // Widget; this won’t compile
{
swap(a.pImpl, b.pImpl); // to swap Widgets, just swap
} // their pImpl pointers
}
这个函数开头的 "template<>" 表明这是个针对 std::swap 的完全模板特化(total template specialization)(某些书中称为“full template specialization”或“complete template specialization”——译者注),函数名后面的 "<Widget>" 表明特化是在 T 为 Widget 类型时发生的。换句话说,当通用的 swap 模板用于 Widgets 时,就应该使用这个实现。通常,我们改变 std namespace 中的内容是不被允许的,但允许为我们自己创建的类型(就像 Widget)完全特化标准模板(就像 swap)。这就是我们现在在这里做的事情。
可是,就像我说的,这个函数还不能编译。那是因为他试图访问 a 和 b 内部的 pImpl 指针,而他们是 private 的。我们能够将我们的特化声明为友元,但是惯例是不同的:让 Widget 声明一个名为 swap 的 public 成员函数去做实际的交换,然后特化 std::swap 去调用那个成员函数:
class Widget { // same as above, except for the
public: // addition of the swap mem func
...
void swap(Widget& other)
{
using std::swap; // the need for this declaration
// is explained later in this Item
swap(pImpl, other.pImpl); // to swap Widgets, swap their
} // pImpl pointers
...
};
namespace std {
template<> // revised specialization of
void swap<Widget>(Widget& a, // std::swap
Widget& b)
{
a.swap(b); // to swap Widgets, call their
} // swap member function
}
这个不但能够编译,而且和 STL 容器保持一致,任何 STL 容器都既提供了 public swap 成员函数,又提供了 std::swap 的特化来调用这些成员函数。
可是,假设 Widget 和 WidgetImpl 是类模板,而不是类,或许因此我们能够参数化存储在 WidgetImpl 中的数据类型:
template<typename T>
class WidgetImpl { ... };
template<typename T>
class Widget { ... };
在 Widget 中加入一个 swap 成员函数(假如我们需要,在 WidgetImpl 中也加一个)就像以前相同容易,但我们特化 std::swap 时会碰到麻烦。这就是我们要写的代码:
namespace std {
template<typename T>
void swap<Widget<T> >(Widget<T>& a, // error! illegal code!
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇: C 箴言:理解Terminology术语
下一篇: C 箴言:用非成员非友元函数取代成员函数
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash