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

int main(int argc, _TCHAR* argv[])
{
int a[] = {,,,,,,,,,};
vector<int> vec1(a, a + );
vector<int> vec2(a + , a + ); swap(vec1, vec2); for (int i = ; i < vec1.size(); i++)
{
cout<<vec1[i]<<" ";
}
return ;
}

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

一、swap的原理

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

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

二、swap的缺陷

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

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

三、swap的改进方案

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

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

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

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

在这个代码中,“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(),y(){};
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(, , , ), l2(, , , );
std::swap(l1, l2);
l1.Print(); return ; }

其中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(),y(){};
    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(, , , ), l2(, , , );
    std::swap(l1, l2);
    l1.Print(); return ; }

其实就是将方案二的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而言全新的东西。

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

  1. [Effective C++ --025]考虑写出一个不抛异常的swap函数

    引言 在我的上一篇博客中,讲述了swap函数. 原本swap只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性. 一.swap函数 标准库的swap函数如下: namespa ...

  2. Effective C++:条款25:考虑写出一个不抛异常的swap函数

    (一) 缺省情况下swap动作可由标准程序库提供的swap算法完毕: namespace std { template<typename T> void swap(T& a, T& ...

  3. Effective C++ Item 25 考虑写出一个不抛异常的swap函数

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛 ...

  4. EC读书笔记系列之13:条款25 考虑写出一个不抛异常的swap函数

    记住: ★当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定其不抛出异常 ★若你提供一个member swap,也该提供一个non-member swap来调用前者.对于cla ...

  5. Effective C++ -----条款25:考虑写出一个不抛异常的swap函数

    当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常. 如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于cla ...

  6. 【25】考虑写出一个不抛异常的swap函数

    1.swap交换对象值,std提供了实现方法模版的实现,它的实现是交换对象值. namespace std { template<typename T> void swap(T& ...

  7. 考虑实现一个不抛异常的swap

    Effective C++:参考自harttle land 类的swap实现与STL容器是一致的:提供swap成员函数, 并特化std::swap来调用那个成员函数. class Widget { p ...

  8. 输入一个数字n 如果n为偶数则除以2,若为奇数则加1或者减1,直到n为1,求最少次数 写出一个函数

    题目: 输入一个数字n  如果n为偶数则除以2,若为奇数则加1或者减1,直到n为1,求最少次数  写出一个函数 首先,这道题肯定可以用动态规划来解, n为整数时,n的解为 n/2 的解加1 n为奇数时 ...

  9. java————数组 简单写出一个管理系统

    数组的特点 1,  数组是一块连续的空间,下标描述空间的位置. 2,  下标从0开始,最大下标为数组长度—1.(*.length-1) 3,  数组元素都是变量.(就是每个下标对应的内容).变量的类型 ...

随机推荐

  1. 实用的Javascript获取网页屏幕可见区域高度

    本文转载原地址:这里 document.body.clientWidth ==> BODY对象宽度 document.body.clientHeight ==> BODY对象高度 docu ...

  2. 如何选择分布式事务形态(TCC,SAGA,2PC,基于消息最终一致性等等)

    各种形态的分布式事务 分布式事务有多种主流形态,包括: 基于消息实现的分布式事务 基于补偿实现的分布式事务 基于TCC实现的分布式事务 基于SAGA实现的分布式事务 基于2PC实现的分布式事务 这些形 ...

  3. JAVA 分布式 - 分布式介绍

    什么是分布式系统? 要理解分布式系统,主要需要明白一下2个方面: 1.分布式系统一定是由多个节点组成的系统. 其中,节点指的是计算机服务器,而且这些节点一般不是孤立的,而是互通的. 2.这些连通的节点 ...

  4. mocha、chai、sinon和istanbul实现100%单元测试覆盖率

    敏捷软件开发中,最重要实践的就是测试驱动开发,在单元测试层面,我们试着实现一个重要的指标就是测试覆盖率.测试覆盖率衡量我们的代码是否已经全部被测试到了. 但是指标本身不是目的,借助测试覆盖率检查,我们 ...

  5. Oracle EBS 创建资产报错

    Solution:设置资产弹性域 随便输入一个值 再冻结

  6. 在html的JavaScript部分计算,保留小数点后面的位数

    例: f_pbf = ((f_boday_fat/f_weight)*100).toFixed(1);      注:例子中的.toFixed(1)是所用函数,确保在所得结果中保留小数点后面一位数,若 ...

  7. Azure Cosmos DB 使用费用参考

    之前在学习Cosmos DB 中SQL API(DocumentDB) 的时候,也就是之前做的一些笔记,看到有使用费用的一些介绍,就有兴趣的去了解了下,做了一下简单的总结. 想了解更多或是购买使用的还 ...

  8. [UI] 精美UI界面欣赏[2]

    精美UI界面欣赏[2]

  9. iOS系统声音列表

    iOS系统声音列表 效果 说明 1. 点击cell就能发出声音 2. 只需要给出声音编号,就可以,非常简单易用 源码 https://github.com/YouXianMing/iOS-Utilit ...

  10. django的admin后台注册model并显示

    在admin后台注册model并显示其他字段: 修改app下的admin.py,注册model: from .models import vmadmin.site.register(vm) #注册名为 ...