实现类类型的重载版本swap

来源:互联网 发布:ubuntu ipython使用 编辑:程序博客网 时间:2024/06/10 23:20
/*
 * 缺省的swap方法实现版十分平淡, 不保证提供可接受的效率:
 *
  template<typename T>
    inline void
    swap(T& a, T& b)
    {
      T tmp(a);
      a = b;
      b = tmp;
    }
 * 使用std::swap方法的类型必需要直接copy constructor 和 copy assignment
 * "以指针指向一个对象,内含真正的数据" 的类型, 用此实现则感觉效率不足,复制动作无一必要
 */
class WidgetImpl
{
public:
    WidgetImpl(int first) : a(first)
    {
        vec.push_back(first);
    }
    WidgetImpl(const WidgetImpl& rhs)
    {
        std::cout << "WidgetImpl(const WidgetImpl& rhs)" << endl;
        a = rhs.a;
        b = rhs.b;
        c = rhs.c;
    }
    WidgetImpl & operator =(const WidgetImpl& rhs)
    {
        std::cout << "WidgetImpl & operator =" << endl;
        a = rhs.a;
        b = rhs.b;
        c = rhs.c;
        return *this;
    }
public:
    int a, b, c;
    std::vector<int> vec;    //意谓着复制的时间很长
};
class Widget
{
public:
    friend std::ostream & operator <<(std::ostream & cout, const Widget& w);
    Widget(int first)
    {
        pImpl = new WidgetImpl(first);
    }
    Widget(const Widget& rhs)
    {
        std::cout << "Widget(const Widget& rhs)" << endl;
        pImpl = new WidgetImpl(rhs.pImpl->a);
    }
    Widget& operator= (const Widget& rhs)
    {
        std::cout << "Widget& operator= (const Widget& rhs)" << endl;
        //
        *pImpl = *(rhs.pImpl);
        return *this;
    }
    void swap(Widget& other)
    {
        // using声明外部相同名称的定义将会被屏蔽
        // 直接调用std::swap(pImpl, other.pImpl); 方法区别是什么?
        // 如果 pImpl 类型拥有特定的swap版本, 它会因限定符而失去性能优化的机会.
        // 这里正确的做法是将 std::swap 在函数内曝光,但是否选择此版本,交由编译器来处理
        using std::swap;
        swap(pImpl, other.pImpl);
    }
private:
    WidgetImpl *pImpl;
};
// cout
std::ostream & operator <<(std::ostream & cout, const Widget& w)
{
    cout << "Widget: WidgetImpl *" << w.pImpl << endl;
    cout << "a :" << w.pImpl->a << endl;
    return cout;
}
/* 我们希望告诉std::swap 当Widget被置换时真正要做的是置换内部的pImpl指针.
 * 确切实现的思路是: 将std::swap针对 Widget特化, 下面是基本构想, 但目前这个形式无法编译通过(effective C++ 访问了私有成员pImpl)
 * template <> 表示它是一个全特化版本 (注意与偏特化的语法区分, 函数模板不支持偏特化, 类模板支持)
 * 通常我们不被允许改变std命名空间内的任何东西, 但被允许为标准模板(如swap)制造特化版本, 使它专属于我们的类.
namespace std
{
    template <>
    void swap<Widget>(Widget& a, Widget& b)
    {
        //调用 std::swap 函数模板的指针类型实例化版本
        swap(a.pImpl, b.pImpl);
    }
}
 */
/* 进一步实现, 解决私有成员访问问题, 不使用 friend function, 因为友员函数拥有访问私有成员的权限, 有损于封装性, 参见effective C++
 * 这里实现的方法是 Widget 提供一个公有成员 swap 来实现真正的操作
 * 这种做法不仅能够通过编译,而且与STL 容器具有一致性, 因为所有的STL容器都提供 public swap成员函数与 std::swap 特化版本.
 */
namespace std
{
    template <>
    void swap<Widget>(Widget& a, Widget& b)
    {
        a.swap(b);
    }
}
/* 然而假设 Widget 和 WidgetImpl 都是 class template 而非 classes.
template <typename T>
class WidgetImpl
{
};
template <typename T>
class Widget
{
public:
    void swap(Widget<T>& other)
    {
        using std::swap;
        swap(pImpl, other.pImpl);
    }
};
 * 在 Widget 内实现的swap实现良好, 但特化std::swap时遇到了乱流
 * 看起来合情合理, 却不合法
 * C++ 只允许对class template进行偏特化, 在function template上偏特化是行不通过的
 * 这段代码不应该通过编译 (虽然某些编译器错误的接受了它)
namespace std
{
    template <typename T>
    void swap< Widget<T> >(Widget<T>& a, Widget<T>& b)
    {
        a.swap(b);
    }
}
 * 通常当你打算偏特化一个function template 时, 惯常做法是简单的为它添加一个重载版本
namespace std
{
    template <typename T>
    void swap(Widget<T>& a, Widget<T>& b)
    {
        a.swap(b);
    }
}
 * 一般重载function template 没有问题, 但
 * std是个特殊的命名空间, 其管理规则也比较特殊. 客户可以全特化std内在templates, 但不可以添加新的templates(或classes或function或其他东西)到std里面.
 * 虽然跨越红线的程序几乎仍可编译和执行, 但它们的行为没有明确的定义.
 * 如果希望你的软件有可预期的行为, 请不要添加任何东西到std中
 * 那么最终可行的方案如下:
namespace WidgetStuff
{
    template <typename T>
    class WidgetImpl
    {
    };
    template <typename T>
    class Widget
    {
    public:
        void swap(Widget<T>& other)
        {
            using std::swap;
            swap(pImpl, other.pImpl);
        }
    };
    // 不再声明swap 为std::swap的特化或重载版本
    // 而是通过c++ 命名空间的名称查找原则, 详见c++ primer
    template <typename T>
    void swap(Widget<T>& a, Widget<T>& b)
    {
        a.swap(b);
    }
}
 * 最终实现的版本的预期行为如下:
 * 当一个类型存在其专属版本时, 就调用其专属版本swap, 否则调用 std::swap的一般版本.
 *
 * 最后: 我们费尽心机实现的swap版本是否有意义, 实现一个不同名的mySwap方法不也可以么?
 * 站在客户的角度上思考, 我们不希望自己编译模板方法是需要再针对某个类型实现特殊的代码, 这件事情很有必要.
    template <typename T>
    void doingSomething(T& obj1, T& obj2)
    {
        ...
        swap(obj1, obj2);
        ...
    }
 *
 */
int main()
{
    // 使用默认std::swap函数模板的<int>实例化实现, 即拷贝,赋值
    int i(1), j(2);
    cout << "i: " << i << " j: " << j << endl;
    swap(i, j);
    cout << "i: " << i << " j: " << j << endl;
    Widget a(1), b(2);
    cout << a << b;
    // 通过打印看出过程中调用拷贝构造函数和赋值运算符,包括子成员, 代价相当高, 效率不可接受. 实际真正操作只要实现Widget 对象的指针成员互换即可.
    // 此处调用显示指定std::命名空间, 与实现特定的swap方法相区分.
    std::swap(a, b);
    cout << a << b;
    return 0;
}