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

(点击此处,接上篇)

那么该怎么做呢?我们依然需要一个方法,既使其他人能调用 swap,又能让我们得到更高效的 template-specific(模板专用)版本。答案很简单。我们依然声明一个 non-member(非成员)swap 来调用 member(成员)swap,只是不再将那个 non-member(非成员)声明为 std::swap 的 specialization(特化)或 overloading(重载)。例如,如果我们的 Widget-related(Widget 相关)机能都在 namespace WidgetStuff 中,它看起来就像这个样子:

namespace WidgetStuff {
  ...                                     // templatized WidgetImpl, etc.

  template<typename T>                    // as before, including the swap
  class Widget { ... };                   // member function

  ...

  template<typename T>                    // non-member swap function;
  void swap(Widget<T>& a,                 // not part of the std namespace
            Widget<T>& b)                                        
  {
    a.swap(b);
  }
}

现在,如果某处有代码使用两个 Widget 对象调用 swap,C++ 的 name lookup rules(名字查找规则)(特指以 argument-dependent lookup(参数依赖查找)或 Koenig lookup(Koenig 查找)著称的规则)将找到 WidgetStuff 中的 Widget-specific(Widget 专用)版本。而这正是我们想要的。

这个方法无论是对于 classes(类)还是对于 class templates(类模板)都能很好地工作,所以看起来我们应该总是使用它。不幸的是,此处还是存在一个需要为 classes 特化 std::swap 的动机(过一会儿我会讲到它),所以如果你想让你的 swap 的 class-specific(类专用)版本在尽可能多的上下文中都能够调用(而你也确实这样做了),你就既要在你的 class 所在的 namespace 中写一个 non-member(非成员)版本,又要提供一个 std::swap 的 specialization(特化)。

顺便提一下,如果你不使用 namespaces(名字空间),上面所讲的一切依然适用(也就是说,你还是需要一个 non-member(非成员)swap 来调用 member(成员)swap),但是你为什么要把你的 class(类),template(模板),function(函数),enum,enumerant(枚举)(此处作者连用了两个词 (enum, enumerant),不知有何区别——译者注)和 typedef names 都堆在 global namespace(全局名字空间)中呢?你觉得合适吗?

迄今为止我所写的每一件事情都是适用于 swap 的作成者的,但是有一种状况值得从客户的观点来看一看。假设你在需要交换两个 objects 的值的地方写了一个 function template(函数模板):

template<typename T>
void doSomething(T& obj1, T& obj2)
{
  ...
  swap(obj1, obj2);
  ...
}

哪一个 swap 会被调用呢?std 中的通用版本,你知道它必定存在;std 中的通用版本的一个 specialization(特化),可能存在,也可能不存在;T-specific(T 专用)版本,可能存在,也可能不存在,可能在一个 namespace(名字空间)中,也可能不在一个 namespace(名字空间)中(但是肯定不在 std 中)。究竟该调用哪一个呢?如果 T-specific(T 专用)版本存在,你希望调用它,如果它不存在,就回过头来调用 std 中的通用版本。如下这样就可以满足你的愿望:

template<typename T>
void doSomething(T& obj1, T& obj2)
{
  using std::swap;           // make std::swap available in this function
  ...
  swap(obj1, obj2);          // call the best swap for objects of type T
  ...
}

当编译器看到这个 swap 调用,他会寻找正确的 swap 来调用。C++ 的 name lookup rules(名字查找规则)确保能找到在 global namespace(全局名字空间)或者与 T 同一个 namespace(名字空间)中的 T-specific(T 专用)的 swap。(例如,如果 T 是 namespace WidgetStuff 中的 Widget,编译器会利用 argument-dependent lookup(参数依赖查找)找到 WidgetStuff 中的 swap。)如果 T-specific(T 专用)swap 不存在,编译器将使用 std 中的 swap,这归功于 using declaration 使 std::swap 在此函数中可见。然而,尽管如此,相对于通用模板,编译器还是更喜欢 T-specific(T 专用)的 std::swap 的 specialization(特化),所以如果 std::swap 针对 T 进行了特化,则特化的版本会被使用。

得到正确的 swap 调用是如此地容易。你需要小心的一件事是不要对调用加以限定,因为这将影响 C++ 确定应该调用的函数的方式。例如,如果你这样写对 swap 的调用,

std::swap(obj1, obj2);         // the wrong way to call swap

这将强制编译器只考虑 std 中的 swap(包括任何 template specializations(模板特化)),因此排除了获得一个定义在其它地方的更为适用的 T-specific(T 专用)版本的可能性。唉,一些被误导的程序员就是用这种方法限定对 swap 的调用,这也就是为你的 classes 完全地特化 std::swap 很重要的原因:它使得以这种被误导的方式写出的代码可以用到 type-specific(类型专用)的 swap 实现。(这样的代码还存在于现在的一些标准库实现中,所以它将有利于你帮助这样的代码尽可能高效地工作。)

到此为止,我们讨论了缺省的 swap,member(成员)swaps,non-member(非成员)swaps,std::swap 的 specializations(特化),以及对 swap 的调用,所以让我们总结一下目前的状况。

首先,如果 swap 的缺省实现为你的 class(类)或 class template(类模板)提供了可接受的性能,你不需要做任何事。试图交换你的类型的 objects 的任何人都会得到缺省版本的支持,而且能工作得很好。

第二,如果 swap 的缺省实现效率不足(这几乎总是意味着你的 class(类)或 template(模板)使用了 pimpl idiom 的某种变种),就按照以下步骤来做:

  1. 提供一个能高效地交换你的类型的两个 objects 的值的 public(公有)的 swap member function(成员函数)。出于我过一会儿就要解释的动机,这个函数应该永远不会抛出 exception(异常)。
  2. 在你的 class(类)或 template(模板)所在的同一个 namespace(名字空间)中提供一个 non-member(非成员)的 swap。用它调用你的 swap member function(成员函数)。
  3. 如果你写了一个 class(类)(不是 class template(类模板)),就为你的 class(类)特化 std::swap。让它也调用你的 swap member function(成员函数)。

最后,如果你调用 swap,请确保包含一个 using declaration 使 std::swap 在你的函数中可见,然后在调用 swap 时不使用任何 namespace(名字空间)限定。

唯一没有解决的问题就是我的警告——绝不要让 swap 的 member(成员)版本抛出 exceptions(异常)。这是因为 swap 的非常重要的应用之一是为 classes(类)(以及 class templates(类模板))提供强大的 exception-safety(异常安全)保证。Item 29 将提供所有的细节,但是这项技术基于 swap 的 member(成员)版本绝不会抛出异常的假设。这一强制约束仅仅应用在 member(成员)版本上!它不能够应用在 non-member(非成员)版本上,因为 swap 的缺省版本基于 copy construction(拷贝构造)和 copy assignment(拷贝赋值),而在通常情况下,这两个函数都允许抛出异常。如果你写了一个 swap 的自定义版本,那么,典型情况下你不仅要提供一个更有效率的交换值的方法,你还要保证这个方法不会抛出异常。作为一个一般规则,这两个 swap 的特性将紧密地结合在一起,因为高效的 swaps 几乎总是基于 built-in types(内建类型)(诸如在 pimpl idiom 之下的指针)的操作,而对 built-in types(内建类型)的操作绝不会抛出异常。

Things to Remember

  • 如果 std::swap 对于你的类型来说是低效的,请提供一个 swap member function(成员函数)。并确保你的 swap 不会抛出异常。
  • 如果你提供一个 member(成员)swap,请同时提供一个调用 member(成员)的 non-member(非成员)swap。对于 classes(类)(非 templates(模板)),还要特化 std::swap。
  • 调用 swap 时,请为 std::swap 使用一个 using declaration ,然后在调用 swap 时不使用任何 namespace(名字空间)限定。
  • 为 user-defined types(用户定义类型)完全地特化 std 模板没有什么问题,但是绝不要试图往 std 中加入任何全新的东西。

你可能感兴趣的:([翻译] Effective C++, 3rd Edition, Item 25: 考虑支持一个 non-throwing swap(不抛异常的 swap)(下))