[翻译] Effective C++, 3rd Edition, Item 25: 考虑支持一个 non-throwing swap(不抛异常的 swap)(上)

来源:互联网 发布:如何防止sql注入攻击 编辑:程序博客网 时间:2024/05/22 10:57

Item 25: 考虑支持一个 non-throwing swap(不抛异常的 swap

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

swap 是一个有趣的函数。最早作为 STL 的构件被引入,后来它成为 exception-safe programming(异常安全编程)的支柱(参见 Item 29)和压制自赋值可能性的通用机制(参见 Item 11)。因为 swap 太有用了,所以适当地实现它非常重要,但是伴随它的不同寻常的重要性而来的,是一系列不同寻常的复杂性。在本 Item 中,我们就来研究一下这些复杂性究竟是什么样的以及如何对付它们。

swap(交换)两个 objects 的值就是互相把自己的值送给对方。缺省情况下,经由标准的 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;
  }
}

只要你的类型支持拷贝(经由 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)),缺省的 swap 实现就能交换你的类型的 objects,而不需要你做任何特别的支持工作。

然而,缺省的 swap 实现可能不那么酷。它涉及三个 objects 的拷贝:从 atemp,从 ba,以及从 tempb。对一些类型来说,这些副本全是不必要的。对于这样的类型,缺省的 swap 就好像让你坐着快车驶入小巷。

这样的类型中最重要的就是那些主要由一个指针组成的类型,那个指针指向包含真正数据的另一种类型。这种设计方法的一种常见的表现形式是 "pimpl idiom"("pointer to implementation" ——参见 Item 31)。一个使用了这种设计的 Widget class 可能就像这样:

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 objects 的值,我们实际要做的全部就是交换它们的 pImpl pointers(指针),但是缺省的 swap 算法没有办法知道这些。它不仅要拷贝三个 Widgets,而且还有三个 WidgetImpl objects,效率太低了。一点都不酷。

我们想要做的就是告诉 std::swap 当交换 Widgets 的是时候,执行交换的方法就是 swap 它们内部的 pImpl pointers。这种方法的正规说法是:specialize std::swap for Widget(针对 Widget 特化 std::swap)。下面是一个基本的想法,虽然在这种形式下它还不能通过编译:

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::swaptotal template specialization(完全模板特化)(某些书中称为 "full template specialization" 或 "complete template specialization" ——译者注),函数名后面的 "<Widget>" 表明这个 specialization(特化)是在 TWidget 时发生的。换句话说,当通用的 swap template(模板)用于 Widgets 时,就应该使用这个实现。通常,我们不被允许改变 std namespace 中的内容的,但我们被允许为我们自己创建的类型(比如 Widget)完全地特化标准模板(比如 swap)。这就是我们现在在这里做的事情。

可是,就像我说的,这个函数还不能编译。那是因为它试图访问 ab 内部的 pImpl pointers(指针),而它们是 private(私有)的。我们可以将我们的 specialization(特化)声明为一个 friend(友元),但是惯例是不同的:它让 Widget 声明一个名为 swap 的 public member function(公有成员函数)去做实际的交换,然后特化 std::swap 去调用那个 member function(成员函数):

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 containers(容器)保持一致,所有 STL containers(容器)都既提供了 public swap member functions(公有 swap 成员函数),又提供调用这些 member functions(成员函数)的 std::swap 的 specializations(特化)。

可是,假设 WidgetWidgetImpl 是 class templates(类模板),而不是 classes(类),或许因此我们可以参数化存储在 WidgetImpl 中的数据的类型:

template<typename T>
class WidgetImpl { ... };

template<typename T>
class Widget { ... };

Widget 中加入一个 swap member function(成员函数)(如果我们需要,在 WidgetImpl 中也加一个)就像以前一样容易,但我们特化 std::swap 时会遇到麻烦。这就是我们要写的代码:

namespace std {
  template<typename T>
  void swap<Widget<T> >(Widget<T>& a,      // error! illegal code!
                        Widget<T>& b)
  { a.swap(b); }
}

这看上去非常合理,但它是非法的。我们试图 partially specialize(部分特化)一个 function template(函数模板)(std::swap),但是尽管 C++ 允许 class templates(类模板)的 partial specialization(部分特化),但不允许 function templates(函数模板)这样做。这样的代码不能编译(尽管一些编译器错误地接受了它)。

当我们想要 "partially specialize"(“部分地特化”)一个 function templates(函数模板)时,通常做法是简单地增加一个 overload(重载)。看起来就像这样:

namespace std {

  template<typename T>             // an overloading of std::swap
  void swap(Widget<T>& a,          // (note the lack of "<...>" after
            Widget<T>& b)          // "swap"), but see below for
  { a.swap(b); }                   // why this isn't valid code
}

通常,重载 function templates(函数模板)确实很不错,但是 std 是一个特殊的 namespace,而且管理它的规则也是特殊的。它认可完全地特化 std 中的 templates(模板),但它不认可在 std 中增加 new templates(新的模板)(或 classes,或函数,或其它任何东西)。std 的内容由 C++ 标准化委员会单独决定,并禁止我们对他们做出的决定进行增加。而且,禁止的方式使你无计可施。打破这条禁令的程序差不多的确可以编译和运行,但它们的行为是未定义的。如果你希望你的软件有可预期的行为,你就不应该向 std 中加入新的东西。

(本篇未完,点击此处,接下篇)

原创粉丝点击