Item25:考虑写出一个不抛异常的swap函数

0.概述

  • 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
  • 如果提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes (而非templates),请特化std::swap。
  • 调用swap时应针对std::swap使用using声明式,然后就可以直接调用swap。
  • 为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些新东西。

1.标准库提供的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;
    }
}

只要类型T支持copying操作,swap函数就可以置换类型为T的对象。

缺点:涉及三个对象(a,b,temp)的复制

2.pimpl实现的Widget类

class WidgetImpl
{
    public:...
    private://可能会有很多数据,复制时间很长
        int a,b,c;
        std::vector<double> v;
};

使用pimpl手法实现的class:

class Widget//这个class使用pimpl手法
{
    public:
        Widget(const Widget& rhs);
        Widget& operator=(const Widget& rhs)//复制widget时,令它复制其widgetImpl对象
        {
            ...
            *pimpl=*(rhs.pImpl);
        }
    private:
        WidgetImpl* pImpl; //指针,所指对象内含Widget数据
};

要置换两个widget对象值,唯一需要做的就是置换其pImpl指针。但缺省的swap算法不只复制三个widgets,还复制三个 widgetImpl对象。效率低下!

2.1 将std::swap针对widget特化

置换指针的一个做法是:将std::swap针对widget特化。下面是基本构想,但目前这个形式无法通过编译:

namespace std {
    template<> // this is a specialized version
    void swap<Widget>(Widget& a, Widget& b)// of std::swap for when T is Widget 
    {
        swap(a.pImpl, b.pImpl); // to swap Widgets, swap their
    } // pImpl pointers; this won’t compile
}

tamplate<>表示这是一个std::swap的全特化版本,函数名称后的<Widget>表示这一特化版本是针对T是Widget而设计,也就是说当一般性的swap template施加于Widgets身上便会启用这个版本。

通常不允许改变std命名空间内的任何东西,但可以为标准templates制造特化版本,使其专属于我们自己的class。

通不过编译的原因:企图访问a,b的pImpl指针(私有数据成员),可以将这个特化版本声明为friend。

2.2 声明一个public成员函数做置换操作

这里采用的方法是令Widget声明一个swap的public函数做真正的置换工作,将swap特化令其调用该成员函数:

class Widget { // same as above, except for the
public: // addition of the swap mem func
    ...
    void swap(Widget& other)
    {
        using std::swap; // 这个声明很重要
        swap(pImpl, other.pImpl); // 置换指针
    } 
...
};
namespace std {
template<> // revised specialization of
void swap<Widget>(Widget& a, Widget& b)
{
    a.swap(b); // to swap Widgets, call their
} // swap member function
}

这样做可以通过编译,而且与STL容器具有一致性(所有STL容器也都提供有public swap成员函数和std: :swap特化版本(用以调用前者))。

3. 假设Widget和WidgetImpl都是类模板

可以尝试将WidgetImpl内的数据类型加以参数化

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

3.1 函数模板偏特化(错)

回顾一下2.2,①在Widget内放一个swap成员函数;②特化std::swap。

第①步很简单,第②步:

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

错误原因:企图偏特化函数模板。C++只允许对类模板偏特化,不能对函数模板偏特化,这段代码无法通过编译。

3.2 为函数模板添加重载版本(不可)

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

一般而言,重载函数模板没有任何问题。但std是一个特殊的命名空间,可以全特化std内的模板,但不可向内添加新的(类、函数或其它)模板。

3.3 新建一个命名空间

namespace WidgetStuff {
    ... // 模板化的WidgetImpl,等
    template<typename T> // 内含swap成员函数
    class Widget { ... }; 
    ...
    template<typename T> // 非成员swap函数;
    void swap(Widget<T>& a, //不属于std命名空间
    Widget<T>& b)
    {
        a.swap(b);
    }
}

现在任何代码想要置换两个Widget对象,C++会找到WidgetStuff内的专属版本

但是如果还需要可以能够调用默认版本:

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作用域或T所在命名空间内的任何T专属的swap。如果T是widget并位于命名空间widgetstuff内,编译器会使用“实参取决之查找规则”( argument-dependent lookup)找出 widgetstuff内的swap。如果没有T专属之swap存在,编译器就使用std内的swap,这得感谢using 声明式让std: :swap在函数内曝光。

4. 总结

首先,如果swap的缺省实现对你的class或class template提供可接受的效率,你不需要额外做任何事。

其次,如果缺省版本的效率不足,可以:

  • 提供一个public swap成员函数,让它高效地置换你的类型的两个对象值。这个函数绝不该抛出异常
  • 在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数。
  • 如果你正编写一个class(而非class template),为你的class特化 std::swap。并令它调用你的swap成员函数。

最后,如果调用swap,请确定包含一个using声明式,以便让 std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸地调用swap。

note:<成员版>swap绝不可抛出异常
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值