1 问题背景

    当交换两个包含了指针成员的类,我们最想看到的是直接交换其指针。但是当我们调用std::swap标准库这个模板函数时,通常它都会复制3个指针指向的对象作为交换所用,缺乏效率。如下:

 namespace std{
template<typename T>
void swap(T& a, T& b) //std::swap的典型实现
{
T temp(a); //一次拷贝,两次赋值
a = b;
b = temp;
}
}
    上面的代码,5行的调用了类的拷贝构造函数将a拷贝给temp,6、7行调用了拷贝赋值函数交换a、b对象。
    那么我们能不能自定义一个较高效率的属于我们自己类的swap函数呢?

2 自定义高效的swap函数

    我们可以为自己写的新类T提供一个高效的swap方法。一般来说,提供swap方法有两种。

2.1 swap成员函数

2.1.1 方法

(1)在我们写的类T中添加一个swap成员函数,这样方便我们交换类中的私有成员,并且设置swap函数不会抛出异常,为什么?见《C++ Primer 第五版中文版》474页。

void T::swap(T& t) noexcept;
(2)在类T的同一命名空间里添加一个非成员函数swap,用于调用类中的成员函数swap

 void swap(T& a, T& b) noexcept
{
a.swap(b);
}

2.1.2 典型实现

 #include <iostream>
#include <string>
class ClassTest{
public:
friend std::ostream& operator<<(std::ostream &os, const ClassTest& s);
friend void swap(ClassTest &a, ClassTest &b) noexcept;
ClassTest(std::string s = "abc") :str(new std::string(s)){} //默认构造函数
ClassTest(const ClassTest &ct) :str(new std::string(*ct.str)){} //拷贝构造函数
ClassTest &operator=(const ClassTest &ct) //拷贝赋值函数
{
str = new std::string(*ct.str);
return *this;
}
~ClassTest() //析构函数
{
delete str;
}
void swap(ClassTest &t) noexcept
{
using std::swap;
swap(str,t.str); //交换指针,而不是string数据
}
private:
std::string *str; //一个指针资源
};
std::ostream& operator<<(std::ostream &os,const ClassTest& s)
{
os << *s.str;
return os;
}
void swap(ClassTest &a, ClassTest &b) noexcept
{
a.swap(b);
}
int main(int argc, char const *argv[])
{
ClassTest ct1("ct1");
ClassTest ct2("ct2");
std::cout << ct1 << std::endl;
std::cout << ct2 << std::endl;
swap(ct1, ct2);
std::cout << ct1 << std::endl;
std::cout << ct2 << std::endl;
return ;
}
注意:
  1. (2)中的swap函数需要和类T位于同一的命名空间里,否则外部调用swap函数可能会解析不到
  2. swap函数最好使它不要抛出异常,就像移动构造函数和移动赋值函数一样。
  3. (2)中的函数可以声明为类T的友元函数,并且设置为内联函数
  4. 做真实交换的swap函数,需要使用using std::swap;

2.1.2 关于using std::swap

 void swap(ClassTest &t) noexcept
{
using std::swap;
swap(str, t.str); //交换指针,而不是string数据
}
    在这里为什么要使用using std::swap呢?也就是using std::swap的作用是什么?
    在C++里存在多种名字查找规则:
  1. 普通的名字查找:先在当前的作用域里查找,查找不到再到外层作用域中查找,即局部相同名字的变量或函数会隐藏外层的变量或作用域
  2. 实参相关的名字查找(ADL)(链接2)
  3. 涉及函数模板匹配规则:一个调用的候选函数(关于候选函数请参考C++ Primer第五版中关于函数的一章)包括所有模板实参推断成功的函数模板实例;候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板;如果恰好有一个函数(或模板)比其他函数更加匹配,则选择该函数;同样好的函数里对于有多个函数模板和只有一个非模板函数,会优先选择非模板函数;同样好的函数里对于没有非模板函数,那么选择更特例化的函数模板
  4. 因为C++会优先在当前的作用域里查找,所以使用using std::swap将标准库的swap模板函数名字引入该局部作用域,重载当前作用域的同名函数,隐藏外层作用域的相关声明。为什么using std::swap不会隐藏外层的void swap(ClassTest &a, ClassTest &b) noexcept函数呢?参见:这里。其中说到,当经过普通的名字查找后(没有包括ADL),如果候选函数中有类成员、块作用域中的函数声明(不包括using声明引入的)、其他同名的函数对象或变量名,则不启动ADL查找了。如果没有,则进行ADL查找。因此在经过普通的查找后,发现并没有匹配的函数,最后再经过ADL找到了标准库中的swap和外层作用域的void swap(ClassTest &a, ClassTest &b) noexcept,由于后者较匹配,编译器优先选择后者。
  5. 如果str类型有自定义的swap函数,那么第4行代码的swap调用将会调用str类型自定义的swap函数
  6. 但是如果str类型并没有特定的swap函数,那么第4行代码的swap调用将会被解析到标准库的std::swap

2.2 swap友元函数

2.2.1 方法

(1)在T的同一命名空间中定义一非成员的swap函数,并且将函数声明为类T的友元函数,方便访问T的私有成员

 void swap(ClassTest &a, ClassTest &b) noexcept
{
using std::swap;
//swap交换操作
}

2.2.2 典型实现

 #include <iostream>
#include <string>
class ClassTest{
public:
friend std::ostream& operator<<(std::ostream &os, const ClassTest& s);
friend void swap(ClassTest &a, ClassTest &b) noexcept;
ClassTest(std::string s = "abc") :str(new std::string(s)){} //默认构造函数
ClassTest(const ClassTest &ct) :str(new std::string(*ct.str)){} //拷贝构造函数
ClassTest &operator=(const ClassTest &ct) //拷贝赋值函数
{
str = new std::string(*ct.str);
return *this;
}
~ClassTest() //析构函数
{
delete str;
}
private:
std::string *str; //一个指针资源
};
std::ostream& operator<<(std::ostream &os, const ClassTest& s)
{
os << *s.str;
return os;
}
void swap(ClassTest &a, ClassTest &b) noexcept
{
using std::swap;
swap(a.str,b.str); //交换指针,而不是string数据
}
int main(int argc, char const *argv[])
{
ClassTest ct1("ct1");
ClassTest ct2("ct2");
std::cout << ct1 << std::endl;
std::cout << ct2 << std::endl;
swap(ct1, ct2);
std::cout << ct1 << std::endl;
std::cout << ct2 << std::endl;
return ;
}
注意:
  1. (2)中的swap函数需要和类T位于同一的命名空间里,否则外部调用swap函数可能会解析不到
  2. swap函数最好使它不要抛出异常,就像移动构造函数和移动赋值函数一样

本文链接:【原创】C++之swap(1)http://www.cnblogs.com/cposture/p/4939971.html

【原创】C++之自定义高效的swap(1)的更多相关文章

  1. C++-高效的swap

    原始版本: template<typename T> void swap(T& a, T& b) { T tmp(a); a = b; b = tmp; } 此版本不重视效 ...

  2. 安装Ubuntu时,遇到自定义交换空间swap大小设置问题

    【整理】Ubuntu自定义分区设置 在安装Ubuntu时,如果使用的是一个新硬盘那么安装向导会建议你使用整个硬盘,如果硬盘上已经有数据了,向导会建议使用剩余的空间。不管怎样,是由向导自动划分的分区。 ...

  3. c++下为使用pimpl方法的类编写高效的swap函数

    swap函数是c++中一个常用的函数,用于交换两对象的值,此外还用于在重载赋值运算符中处理自赋值情况和进行异常安全性编程(见下篇),标准模板库中swap的典型实现如下: namespace stl { ...

  4. 【原创】Android自定义适配器的使用方法

    比如说我们已经得到了数据,想在一个listview或者在其他的控件中显示的,并且我们显示想要自己设计样式来显示的话就要用到自定义适配器了,下面让我们结合代码讲一下具体的使用方法: 代码会有注释的哦: ...

  5. 读书笔记 effective c++ Item 25 实现一个不抛出异常的swap

    1. swap如此重要 Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11).Sw ...

  6. VSCode 必装的 10 个高效开发插件

    本文介绍了目前前端开发最受欢迎的开发工具 VSCode 必装的 10 个开发插件,用于大大提高软件开发的效率. VSCode 的基本使用可以参考我的原创视频教程「VSCode 高效开发必装插件」. V ...

  7. VSCode高效开发插件

    VSCode 必装的 10 个高效开发插件 https://www.cnblogs.com/parry/p/vscode_top_ten_plugins.html 本文介绍了目前前端开发最受欢迎的开发 ...

  8. swap function & copy-and-swap idiom

    在C++中,众所周知在一个资源管理类(例如含有指向堆内存的指针)中需要重新定义拷贝构造函数.赋值运算符以及析构函数(Big Three),在新标准下还可能需要定义移动构造函数和移动赋值运算符(Big ...

  9. [Swift]LeetCode24. 两两交换链表中的节点 | Swap Nodes in Pairs

    Given a linked list, swap every two adjacent nodes and return its head. Example: Given 1->2->3 ...

随机推荐

  1. jquery-能拖拽宽度的table

    控件官方地址:http://www.bacubacu.com/colresizable/?utm_source=jquer.in&utm_medium=website&utm_camp ...

  2. linux 解压 压缩 常见命令

    压缩命令: .tar tar -cvf 文件名称.tar 文件或者文件夹 .tar.gz tar -zcvf 文件名称.tar.gz 文件或者文件夹 .tar.xz tar -Jcf 文件名称.tar ...

  3. Windows批处理命令学习中遇到的坑--持续更新中

    再次拾起windows批处理命令,下边将一些遇到的小问题写出来,希望可以帮到大家 1.set命令:set主要的作用是为变量赋值,类似于编程语言中的var i = Value:但是在使用的过程中一定要注 ...

  4. 从Typescript看原型链

    话不多说先来段代码 class Parent { private name:string; constructor(name) { this.name = name; } public getName ...

  5. 《你不知道的JavaScript》三卷读后感

    本系列的作者是Kyle Simpson,上卷译者赵望野.梁杰,中卷译者单业,下卷译者单业.姜南. 我个人觉得第一卷是本系列最好的(必读),而第二卷虽然也讲解了很多知识点,但是对于异步和性能的那部分提及 ...

  6. 学习Python第五天

    今天咱们讲一下元组,hash,字典,元组是数据类型其中之一 元组的特性为有序的,不可变的,但是如果其中有可变元素,这些可变元组还是可以改变的,代码如下: 元组的写法:name = (‘roy’,‘al ...

  7. Spring Boot中使用Spring Security进行安全控制转载来自翟永超

    我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(比如:Apache ...

  8. jdango

    1.jdango的下载 命令行: pip install django ==1.11.18 pip install django ==1.11.18 -i https://pypi.douban.co ...

  9. [编译] 3、在Linux下搭建51单片机的开发烧写环境(makefile版)

    星期二, 10. 七月 2018 01:01上午 - beautifulzzzz 一.SDCC(Small Device C Compiler)编译环境搭建 SDCC是一个小型设备的C语言编译器,该编 ...

  10. 包建强的培训课程(1):Android App企业级开发

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...