wap是个有趣的函数。原本它只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来实现自我赋值可能性的一个常见机制。所谓swap两对象值,就是将两对象的值交换。

典型实现

缺省情况下的swap动作可有标准库提供的swap算法完成:

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

只要类型T支持拷贝(通过拷贝构造函数和拷贝运算符),缺省的swap就可以完成交换两相同类型的对象的值,你不需要为此做任何动作。

  • 缺点

上面版本的实现调用了三次拷贝,对于某些类型而言,这些拷贝无一必要。其中最主要的就是“以指针指向一个对象,内含指针数据”的那种类型。这种设计的常见表现形式就是"pimpl(pointer to implementation)手法"。比如:

class ContourInfo {//用于存放轮廓的信息
public:
...
private:
   ...//假设有很多数据,复制时间很长
int index;//索引
double area;//面积
Point2f ps[4];//最小外接矩形的四个角点
}; class Contour {
public:
...
private:
ContourInfo* pCon;//指针,所指对象内含Contours数据
};

一旦要置换两个Contour对象的值,我们唯一要做的是交换其pimpl指针,但是缺省的swap不知道这一点。它不止复制三个Contour ,还复制三个ContourInfo 对象,常缺乏效率。

  • 思路

我们希望能够告诉std::swap:当Contour被置换时真正该做的是置换其内部的pImpl指针。确切实现这个思路的一个做法是:将std::swap针对Contour特化。下面是基本构想,但目前这个形式无法通过编译:

namespace std{
template<>
void swap<Contour>(Contour&a, Contour&b){
swap(a.pcon, b.pcon); // 只需要交换它们的pImpl指针就好
};
};

这个函数一开始的template<>表示它是std::swap的一个全特化版本,函数名称后面的<Contour>表示这一特化版本是针对"T是Contour"而设计。换句话说当一般性的swap模板施行于Contour身上就会启用这个版本。通常我们不被允许改变std命名空间内的任何东西,但可以为标准模板(比如swap)制作特化版本,使它专属于我们自己的类(比如Contour).

但是这个版本无法通过编译,因为它企图访问a和b内的的private pImpl指针。

特化实现

我们可以将这个特化版本声明为友元,但是和以往不一样:我们令Contour声明一个名为swap的public成员函数做真正的置换工作,然后将std::swap特化,令它调用该函数

class Contour{
public:
...
void swap(Contour& other){
using std::swap;
swap(pcon, other.pcon);
}
} namespace std{
template<>
void swap<Contour>(Contour&a, Contour& b){//no-memberde swap
a.swap(b);//调用swap成员函数
}
};

这种做法不止能够通过编译,还与STL容器有一致性,因为所有STL容器也都提供有public swap成员函数和std::swap特化版本。

  • 示例

为深入理解,写了个例子,将下图中的轮廓按面积大小排序。

代码:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv; class ContourInfo{//用于存放轮廓信息
public:
ContourInfo() {};
ContourInfo(int idx, double a, Point2f p[4]) {
index = idx;
area = a;
for (int i = 0; i < 4; i++) {
ps[i] = p[i];
}
}
double Area() {
return area;
}
double Index() {
return index;
}
private:
int index;
double area;
Point2f ps[4];
}; class Contour {
public:
Contour(ContourInfo& con) {
pCon = new ContourInfo();
*pCon = con;
}
Contour(const Contour& con) {
pCon = new ContourInfo();
*pCon = *(con.pCon);
}
~Contour() {
delete pCon;
}
bool operator< (const Contour& con) {
return pCon->Area() < con.pCon->Area();
}
void swap(Contour& con) {
using std::swap;
swap(pCon, con.pCon);
}
void print() {
cout << "index为" << pCon->Index() << "的轮廓面积为:" << pCon->Area() << endl;
} private:
ContourInfo* pCon;
};
namespace std {
template<>
void swap<Contour>(Contour& a, Contour& b) {
a.swap(b);
}
};
int main() {
Mat src = imread("D:/Backup/桌面/1.png", 0);
vector<vector<Point>> cons;
findContours(src, cons, RETR_LIST, CHAIN_APPROX_SIMPLE);
Point2f ps[4];
vector<Contour> contours;
for (size_t i = 0; i < cons.size(); i++) {
RotatedRect rec = minAreaRect(cons[i]);
rec.points(ps);
ContourInfo temp(i, contourArea(cons[i]), ps);
Contour con(temp);
contours.push_back(con);
}
cout << "-------------排序前------------" << endl;
for (size_t i = 0; i < contours.size(); i++) { contours[i].print();
}
for (size_t i = 0; i < contours.size(); i++) {//冒泡排序
for (size_t j = 0; j < contours.size() - i - 1; j++) {
if (contours[j] < contours[j + 1])
continue;
swap(contours[j], contours[j + 1]);
}
}
cout << "-------------排序后------------" << endl;
for (size_t i = 0; i < contours.size(); i++) {
contours[i].print();
}return 0;
}
  • 注意

构造函数如果这样写:

// 写法1
Contour(ContourInfo& con) {
pCon = &con;
}
Contour(const Contour& con) {
pCon = con.pCon;
} // 写法2
Contour(ContourInfo& con) {
pCon = &con;
}
Contour(const Contour& con) {
pCon = new ContourInfo();
*pCon = *(con.pCon);
} // 写法3
Contour(ContourInfo& con) {
pCon = new ContourInfo();
*pCon = con;
}
Contour(const Contour& con) {
pCon = con.pCon;
} // 写法4
Contour(ContourInfo& con) {
pCon = new ContourInfo();
*pCon = con;
}
Contour(const Contour& con) {
pCon = new ContourInfo();
*pCon = *(con.pCon);
}
~Contour() {
delete pCon;
}

结果为:

原因:

for(...){
ContourInfo temp(i, contourArea(cons[i]), ps);
Contour con(temp);
contours.push_back(con);
}

在for循环中,temp的地址固定。写法1在构造con时,pCon永远指向第一个temp的地址,而temp的信息总是在改变,所以最后vector中每一个轮廓的信息都是最后一个轮廓的信息。写法2新建con的pCon还是永远指向固定的temp地址,但是pushback会调用copy赋值函数,copy赋值函数新建了内存存放轮廓信息。写法3每次新建的con中的pCon指向不同的地址,pushback在copy时,新的指针还是指向之前的地址。写法4才能调用delete,不然我不知道在哪里释放内存。

总结

如果swap的缺省实现码对你的类或者类模板提供可接受的效率,你不需要做任何事情。但是,如果你觉得swap缺省实现版的效率不足(那几乎总是意味着你的类或者模板使用了某种pimpl手法),试着做如下事情:

1. 提供一个public swap成员函数,让他高效地置换你的类型的两个对象值,这个函数绝不该抛出异常。non-member函数是可以抛出异常的。

2. 在你的类或者模板所在的命名空间内提供一个non-member swap,并令它调用上面的swap成员函数

3. 如果你正在编写一个类(而不是类模板),为你的类特化std::swap,并令它调用你的swap成员函数。

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

【Effective C++】设计与声明——考虑写出一个不抛异常的swap函数的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  8. Effective C++ —— 设计与声明(四)

    条款18 : 让接口容易被正确使用,不易被误用 欲开发一个“容易被正确使用,不容易被误用”的接口,首先必须考虑客户可能做出什么样的错误操作.  1. 明智而审慎地导入新类型对预防“接口被误用”有神奇疗 ...

  9. Effective C++ ——设计与声明

    条款18:让接口更容易的被使用,不易误用 接口设计主要是给应用接口的人使用的,他们可能不是接口的设计者,这样作为接口的设计者就要对接口的定义更加易懂,让使用者不宜发生误用,例如对于一个时间类: cla ...

  10. 【C++】从零开始的CS:GO逆向分析3——写出一个透视

    [C++]从零开始的CS:GO逆向分析3--写出一个透视 本篇内容包括: 1. 透视实现的方法介绍 2. 通过进程名获取进程id和进程句柄 3. 通过进程id获取进程中的模块信息(模块大小,模块地址, ...

随机推荐

  1. SharePreferences概念

    概念 SharePreferences是一种轻量级的数据存储方式,它是以key-value的形式保存在 data/data//shared_prefs 下的xml文件中.通常使用它来保存应用中的一些简 ...

  2. linux 忘记密码怎么破?

    前言 适合硬件在自己旁边的,不适合云服务器,云服务器很方便的,可以直接重置密码,因为云服务器都是虚拟机. 正文 1.步骤 进入到可以看到节目的视图,按下esc键进入下面的视图! 2.接着进入下面的界面 ...

  3. Python中2种常用数据可视化库:Bokeh和Altair

    本文分享自华为云社区<探究数据可视化:Bokeh vs. Altair>,作者:柠檬味拥抱. 在数据科学和数据分析领域,数据可视化是一种强大的工具,可以帮助我们更好地理解数据.发现模式和趋 ...

  4. vue3 快速入门系列 —— 组件通信

    vue3 快速入门系列 - 组件通信 组件通信在开发中非常重要,通信就是你给我一点东西,我给你一点东西. 本篇将分析 vue3 中组件间的通信方式. Tip:下文提到的绝大多数通信方式在 vue2 中 ...

  5. 前端使用 Konva 实现可视化设计器(6)

    请大家动动小手,给我一个免费的 Star 吧~ 这一章处理一下复制.粘贴.删除.画布归位.层次调整,通过右键菜单控制. github源码 gitee源码 示例地址 复制粘贴 复制粘贴(通过快捷键) / ...

  6. axios请求时获取不到错误提示问题。

    前端方面使用axios请求,由于新增时,有的条件格式填写错误.后端返回412状态码. ,axios可能封装不完善,他获取数据使状态码为4开头的统统不暴露出去,导致请求时,412这样的状态码,获取不到里 ...

  7. 聊聊 Linux iowait

    哈喽大家好,我是咸鱼. 我们在使用 top 命令来查看 Linux 系统整体 CPU 使用情况的时候,往往看的是下面这一列: %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 ...

  8. [FAQ] golang-migrate/migrate error: default addr for network '127.0.0.1:3306' unknown

    按照项目github文档上所示,在使用 mysql 时你可能会这样写: $ migrate -path db/migrations -database mysql://root:123456@127. ...

  9. 举例useContext性能低下的样例,同时推荐用什么方法改进

    在React中,useContext 是一种非常方便的全局状态管理工具,它可以让我们在组件之间共享状态,而不需要通过层层传递 props.然而,当我们在一个大型的 React 应用中过度使用 useC ...

  10. docker 完美部署gitea

    效果: docker-compose version: "3" networks: gitea: external: false services: server: image: ...