《Effective C++》item25:考虑写出一个不抛异常的swap函数

 std::swap()是个很有用的函数,它可以用来交换两个变量的值,包括用户自定义的类型,只要类型支持copying操作,尤其是在STL中使用的很多,例如:

int main(int argc, _TCHAR* argv[])
{
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    vector<int> vec1(a, a + 4);
    vector<int> vec2(a + 5, a + 10);  

    swap(vec1, vec2);  

    for (int i = 0; i < vec1.size(); i++)
    {
        cout<<vec1[i]<<" ";
    }
    return 0;
}  

 

        上面这个例子实现的是两个vector的内容的交换,有了swap函数,省去了很多的麻烦!What a fucking convenient!

 

 

一、swap的原理

 

        缺省的swap的原理其实很简单,就是将两对象的值彼此赋予对方,其实现过程大致如下:

 

namespace std{
    template<typename T>
    void swap(T&a, T& b){
        T temp(a);
        a=b;
        b=temp;
    }
} 

 

        swap的实现是通过被交换类型的copy构造函数和赋值操作符重载实现的,会涉及到三个对象的复制。所以说,要对自定义的类型调用swap实现交换,必须首先保证自定义类型的copy构造函数和赋值操作符重载函数。

 

 

二、swap的缺陷

 

        缺省的swap最主要的问题就是:当对象内部包含指针成员时,它不仅要复制3三次被交换的对象,还要复制3次对象成员,而且复制的是指针对象所指向的内容!例如:

 

// SwapTest.cpp : 定义控制台应用程序的入口点。  

#include "stdafx.h"
#include <iostream>
#include <vector>  

using namespace std;  

class Point{
private:
    int x,y;
public:
    Point():x(0),y(0){};
    Point(int a, int b):x(a),y(b){};
    void Print(){
        cout<< x << " "<< y <<endl;
    }
    int GetX(){
        return x;
    }
    int GetY(){
        return y;
    }
};  

class Line{
private:
    Point *px, *py;
public:
    Line():px(),py(){};
    Line(int a,int b,int c,int d):px(new Point(a,b)), py(new Point(c, d)){};
    Line(const Line& li){
        px = new Point(*li.px);
        py = new Point(*li.py);
    }
    Line& operator=(const Line& li){
        Point *p = px;
        px = new Point(*li.px);
        delete p;
        p = py;
        py = new Point(*li.py);
        delete p;
        return *this;
    }
    void Print(){
        cout<<"( "<<px->GetX()<<" "<<px->GetY()<<" "<<py->GetX()<<" "<<py->GetY()<<" )"<<endl;
    }
};  

int main(int argc, _TCHAR* argv[])
{
    Line l1(1,1,2,2), l2(3,3,4,4);
    swap(l1, l2);
    l1.Print();
    return 0;
}  

 

        一旦要置换两个Line对象值,swap需要复制三个Line,还要复制六个Point对象,详细可以看赋值运算符重载函数,这样是非常低效的,尤其是当Line的数据成员非常庞大的时候,实际上我们只需要交换各自成员的指针就可以了!

三、swap的改进方案

        我们希望告诉std::swap:当Line被置换时,真正该做的是置换骑内部的px和py指针。实现这个过程有几个方案,我们先看最简单的方案:

方案一:将std::swap针对Line特化

 

        C++规定:通常不允许改变std命名空间内的任何东西,但是可以为标准template(如swap)制造特化版本,使他专属于我们自己的class(例如Line)。

        根据这个性质,我们可以对std:swap针对Line进行特化。我们可以这样特化swap:

namespace std{
    template<>
    void swap<Line>(Line& l1, Line& l2){      // std::swap()的特化版本,std::swap()只可以特化,不可以重载
        cout<<"swap of std is called......"<<endl;
        l1.swap(l2);
    }
}  

        在这个代码中,“template<>”表示它是std::swap的一个全特化版本,函数名之后的“<Line>"表示这一特化版本针对”T是Line”而设计。

 

        完整的方案如下:

 

// SwapTest.cpp : 定义控制台应用程序的入口点。  

#include "stdafx.h"
#include <iostream>
#include <vector>  

using namespace std;  

class Point{
private:
    int x,y;
public:
    Point():x(0),y(0){};
    Point(int a, int b):x(a),y(b){};
    void Print(){
        cout<< x << " "<< y <<endl;
    }
    int GetX(){
        return x;
    }
    int GetY(){
        return y;
    }
};  

class Line{
private:
    Point *px, *py;
public:
    Line():px(),py(){};
    Line(int a,int b,int c,int d):px(new Point(a,b)), py(new Point(c, d)){};
    void swap(Line& l){  //Line成员函数,用以实现指针成员交换
        cout<<"swap of Line is called......"<<endl;
        using std::swap;
        swap(px, l.px);  // 交换指针
        swap(py, l.py);
    }  

    void Print(){
        cout<<"( "<<px->GetX()<<" "<<px->GetY()<<" "<<py->GetX()<<" "<<py->GetY()<<" )"<<endl;
    }
};  

namespace std{
    template<>
    void swap<Line>(Line& l1, Line& l2){      // std::swap()的特化版本,std::swap()只可以特化,不可以重载
        cout<<"swap of std is called......"<<endl;
        l1.swap(l2);
    }
}  

int main(int argc, _TCHAR* argv[])
{
    Line l1(1,1,2,2), l2(3,3,4,4);
    swap(l1, l2);
    l1.Print();
    return 0;
}  

        在这个例子中,一共出现了5次swap这个函数:

 

        第一次是main中调用的swap,这个调用的是我们自定义的std::swap()的特化版本

        第二次是我们自己定义的std::swap()对Line类型的特化,在函数名前面有“template<>”

        第三次是对Line特化的swap中调用的swap,也就是l1.swap(l2),这个很明显是调用Line类型的swap()成员函数

        第四次是Line类型中的成员函数,void swap(Line& l),这个public函数的目的是供给非Line成员函数调用的,也就是特化版本的swap,因为只有类的成员函数才可以调用类的private成员变量

        第五次是Line成员函数swap调用的swap,这个swap调用前面有个using std::swap的声明,表示后面调用的是std中的原始swap,当然不是特化版本的swap
        其中可以被调用的swap有3个,std中原始的swap、std::swap的特化版本、Line中的成员函数swap,这3个函数中,真正给用户调用的只有第一个swap,也就是std::swap的Line特化版。通过这一系列函数就可以实现Line对象中指针成员的指针的交换,而不是Line对象整个的交换。

        这种方式和STL容器有一致性,因为所有的STL容器也都提供有public swap成员函数和std::swap特化版本(用以调用前者)

        

方案二、重载特化的std::swap

 

        上面这种方式是针对Line和Point都是非template class,现在假设Line和Point都是template class,那么这种方式还可不可以了?

        假设Line类和Point类都是template class,如下定义:

#include <iostream>
#include <string.h>  

template<typename T>
class Point{
private:
     T x,y;
public:
     Point():x(0),y(0){};
     Point(T a, T b):x(a),y(b){};
     void Print(){
          std::cout<< x << " "<< y <<endl;
     }
     T GetX(){
          return x;
     }
     T GetY(){
          return y;
     }
};  

template<typename T1, typename T2>
class Line{
private:
     T1 px,py;
public:
     Line():px(),py(){};
     Line(T2 a,T2 b,T2 c,T2 d):px(T1(a, b)), py(T1(c, d)){};
     void Print(){
          std::cout<<"( "<<px.GetX()<<" "<<px.GetY()<<" "<<py.GetX()<<" "<<py.GetY()<<" )"<<std::endl;
     }
     void swap(Line<T1, T2>& l){
          std::cout<<"swap of Line is called......"<<std::endl;
          using std::swap;
          swap(px, l.px);
          swap(py, l.py);
     }
};  

namespace std {
    template<typename t1="" typename="" t2="">
    void swap(Line<t1 t2="">& l1, Line<t1 t2="">& l2){
        cout<<"swap of std is called......"<<std::endl;
        l1.swap(l2);
    }
}  

int main()
{
    Line<Point<double>, double> l1(1, 1, 2, 2), l2(3, 3, 4, 4);
    std::swap(l1, l2);
    l1.Print();  

    return 0;  

}  

 

        其中std里面的swap函数就是对std::swap的一个重载版本,然而,这个方式并不是特别的推荐,按照《effective c++》中的说法,这是一种非法的方式,是被C++标准禁止的,虽然能够编译和运行通过。

方案三、非特化非重载的non-member swap

        我们可以声明一个非Line类成员函数swap,让其调用Line的成员函数swap,这个非成员swap也非特化的std::swap,如下所示:

 

  1. #include "stdafx.h"
    #include <iostream>
    #include <string.h>  
    
    template<typename T>
    class Point{
    private:
        T x,y;
    public:
        Point():x(0),y(0){};
        Point(T a, T b):x(a),y(b){};
        void Print(){
            std::cout<< x << " "<< y <<endl;
        }
        T GetX(){
            return x;
        }
        T GetY(){
            return y;
        }
    };  
    
    template<typename T1, typename T2>
    class Line{
    private:
        T1 px,py;
    public:
        Line():px(),py(){};
        Line(T2 a,T2 b,T2 c,T2 d):px(T1(a, b)), py(T1(c, d)){};
        void Print(){
            std::cout<<"( "<<px.GetX()<<" "<<px.GetY()<<" "<<py.GetX()<<" "<<py.GetY()<<" )"<<std::endl;
        }
        void swap(Line<T1, T2>& l){
            std::cout<<"swap of Line is called......"<<std::endl;
            using std::swap;
            swap(px, l.px);
            swap(py, l.py);
        }
    };  
    
    template<typename T1, typename T2>
    void swap(Line<T1, T2>& a, Line<T1, T2>& b){
        std::cout<<"swap of non-member is called......"<<std::endl;
        a.swap(b);
    }  
    
    int main()
    {
        Line<Point<double>, double> l1(1, 1, 2, 2), l2(3, 3, 4, 4);
        std::swap(l1, l2);
        l1.Print();  
    
        return 0;  
    
    }  

     

        其实就是将方案二的std::swap重载改成了自定义的非成员函数,原理依然一样!

remember:

        1.当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
        2.如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于class(而非template),也请特化std::swap。
        3.调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰符”。
        4.为“用户定义类型”进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

时间: 2024-11-06 07:22:27

《Effective C++》item25:考虑写出一个不抛异常的swap函数的相关文章

怎么写出一个sql语句来表示销售的配件的数量

问题描述 怎么写出一个sql语句来表示销售的配件的数量 String sql = "select '配件A' label,12 value " + "union all " + "select '配件B' label,10 value "; 来修改这个sql来表示表中的内容 解决方案 啥玩意,你这不是已经有这些数据了吗,还要改什么

图片-用c语言写出一个行李托运问题

问题描述 用c语言写出一个行李托运问题 解决方案 请教一个C语言问题关于<c语言的一个小问题>的补充 解决方案二: c语言行李托运问题.

【求助】用Python写出一个遍历网站所有url的小工具

问题描述 [求助]用Python写出一个遍历网站所有url的小工具 用Python 怎么写出一个 遍历网站所有url的小工具, 包括首页的url和所有子网页url 不能有重复的url,我自己写的只能遍历一个网页的 首页,子网页的遍历不出来,新人 没币 ,求好心人帮助!!! 解决方案 http://www.cnblogs.com/fnng/p/3576154.htmlhttp://www.oschina.net/code/list_releted_codes?id=25402 解决方案二: htt

应用-麻烦大神写出一个c++程序出来

问题描述 麻烦大神写出一个c++程序出来 几何图形的简单计算.内容是用C++设计一个业务处理系统,其业务就是进行简单的几何计算:l输入圆的半径,计算并输出圆的周长和面积:l输入圆柱的半径和高,计算并输出圆柱的表面积和体积:l输入圆锥的半径和高,计算并输出圆锥的表面积和体积:l输入圆球的半径,计算并输出圆球的表面积和体积:l输入长方形的长和宽,计算并输出长方形的周长和面积:l输入长方体的长.宽和高,计算并输出长方体的表面积和体积.要求:①必须设计圆.圆柱.圆锥.圆球.长方形.长方体等类:②必须应用

printf-设有一个顺序表A,包含n个元素,要求写出一个将该表逆置的算法,

问题描述 设有一个顺序表A,包含n个元素,要求写出一个将该表逆置的算法, #include #define MaxLen 50 typedef int elemtype: typedef elemtype sqlist [MaxLen]: int create (sqlist A) { int i,n: printf("创建一个顺序表n"): printf("输入元素个数:"): scanf( ): for (i=0:i<n:i++) { printf(&qu

如何写出一个完全和Service层剥离的dao层?

问题描述 如何写出一个完全和Service层剥离的dao层?不要在service层构造hql语句,完全只是传参数,dao层构造hql,如果换了别的持久化框架,那么只需要修改dao层就可以,service完全可以不动,这样体现出来低耦合的软件开发原则 解决方案 在Service层传来参数,使用Map传过来,然后在到层增加一个函数,专门负责在dao层生成hql语句,一般的函数都封装好了,基本要构造hql语句的,都是需要查询的,需要构造key=value形式的,所以map刚好符合,我在dao层就是这么

wsdl-建立webserviceclient,调用接口,自己写出一个xml作为参数传回去,怎么传

问题描述 建立webserviceclient,调用接口,自己写出一个xml作为参数传回去,怎么传 1C 是不是一定要按对方给的xml格式写好xml,只穿几个参数会影响到对方解析参数么,

c语言数组 字符串-n为2到50,如何顺序输出1/n的十进制表示的字符串(写出一个循环节为止)

问题描述 n为2到50,如何顺序输出1/n的十进制表示的字符串(写出一个循环节为止) 输出一个循环周期后就结束该数的输出1/7=0.142857n为2到50,如何顺序输出1/n的十进制表示的字符串(写出一个循环节为止) 解决方案 #include <iostream> using namespace std; void foo(int n) { int x = 1; int * initmod = new int[n]; cout << "1 / " <&

用ASP的方法动态写出JavaScript的表单验证的函数checkSubmit()

javascript|表单验证|动态|函数 <%'请转存为CheckForm_JS.asp使用 '*****************************************************************************'函数名称:CheckForm_JS(frmName,errStr)'功能:用ASP的方法动态写出JavaScript的表单验证的函数checkSubmit()'使用方法:1.<!--Include File=URL+本函数所在的页>;'