【Effective C++】设计与声明——考虑写出一个不抛异常的swap函数
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函数的更多相关文章
- 《Effective C++》item25:考虑写出一个不抛异常的swap函数
std::swap()是个很有用的函数,它可以用来交换两个变量的值,包括用户自定义的类型,只要类型支持copying操作,尤其是在STL中使用的很多,例如: int main(int argc, _T ...
- Effective C++ -----条款25:考虑写出一个不抛异常的swap函数
当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常. 如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于cla ...
- [Effective C++ --025]考虑写出一个不抛异常的swap函数
引言 在我的上一篇博客中,讲述了swap函数. 原本swap只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性. 一.swap函数 标准库的swap函数如下: namespa ...
- Effective C++:条款25:考虑写出一个不抛异常的swap函数
(一) 缺省情况下swap动作可由标准程序库提供的swap算法完毕: namespace std { template<typename T> void swap(T& a, T& ...
- EC读书笔记系列之13:条款25 考虑写出一个不抛异常的swap函数
记住: ★当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定其不抛出异常 ★若你提供一个member swap,也该提供一个non-member swap来调用前者.对于cla ...
- Effective C++ Item 25 考虑写出一个不抛异常的swap函数
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛 ...
- 【25】考虑写出一个不抛异常的swap函数
1.swap交换对象值,std提供了实现方法模版的实现,它的实现是交换对象值. namespace std { template<typename T> void swap(T& ...
- Effective C++ —— 设计与声明(四)
条款18 : 让接口容易被正确使用,不易被误用 欲开发一个“容易被正确使用,不容易被误用”的接口,首先必须考虑客户可能做出什么样的错误操作. 1. 明智而审慎地导入新类型对预防“接口被误用”有神奇疗 ...
- Effective C++ ——设计与声明
条款18:让接口更容易的被使用,不易误用 接口设计主要是给应用接口的人使用的,他们可能不是接口的设计者,这样作为接口的设计者就要对接口的定义更加易懂,让使用者不宜发生误用,例如对于一个时间类: cla ...
- 【C++】从零开始的CS:GO逆向分析3——写出一个透视
[C++]从零开始的CS:GO逆向分析3--写出一个透视 本篇内容包括: 1. 透视实现的方法介绍 2. 通过进程名获取进程id和进程句柄 3. 通过进程id获取进程中的模块信息(模块大小,模块地址, ...
随机推荐
- 【Nano Framework ESP32篇】WS2812 彩色灯带实验
地球人皆知,许多物联网教程作者的心中都深爱着一灯大师,所以第一个例程总喜欢点灯,高级一点的会来个"一闪一闪亮晶晶".老周今天要扯的也是和灯有关的,但不单纯地点个灯,那样实在不好玩, ...
- etcd 集群安装
1.环境准备 下载安装包:https://github.com/etcd-io/etcd/releases/ 这里下载的安装包为:etcd-v3.5.9-linux-amd64.tar.gz,即我们当 ...
- docker containerd runc containerd-shim等组件的关系
早期 kubelet 创建容器工作原理 因为 docker 出生的比 k8s 早,所以 k8s 早期的容器运行时都是基于 docker 的,kubelet 通过 docker 的 api 创建容器.后 ...
- 剑指offer66(Java)-构建乘积数组(中等)
题目: 给定一个数组 A[0,1,-,n-1],请构建一个数组 B[0,1,-,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×-×A[i ...
- 滴滴 Flink-1.10 升级之路
简介: 滴滴实时计算引擎从 Flink-1.4 无缝升级到 Flink-1.10 版本,做到了完全对用户透明.并且在新版本的指标.调度.SQL 引擎等进行了一些优化,在性能和易用性上相较旧版本都有很大 ...
- 为什么DevOps的必然趋势是BizDevOps
简介: 从精益思想出发,我们可以看到DevOps的必然发展方向,那就是向业务侧延伸.业务是产品开发和运维的源头,完整的价值流必须从源头开始.这不是预测,而是正在发生的事. 编者按:本文源自阿里云云效团 ...
- 微信小程序支付实现流程
基本流程 用户操作流程 小程序流程 整体支付流程 代码实现 创建订单 创建订单,主要是前端将订单的信息提交到后端.但是在创建订单之前还有一些准备工作要做: 获取用户数据GetUserInfo 获取用户 ...
- NoSQL 数据库管理工具,搭载强大支持:Redis、Memcached、SSDB、LevelDB、RocksDB,为您的数据存储提供无与伦比的灵活性与性能!
NoSQL 数据库管理工具,搭载强大支持:Redis.Memcached.SSDB.LevelDB.RocksDB,为您的数据存储提供无与伦比的灵活性与性能! [官网地址]:http://www.re ...
- 【GUI软件】小红书搜索结果批量采集,支持多个关键词同时抓取!
目录 一.背景介绍 1.1 爬取目标 1.2 演示视频 1.3 软件说明 二.代码讲解 2.1 爬虫采集模块 2.2 软件界面模块 2.3 日志模块 三.获取源码及软件 一.背景介绍 1.1 爬取目标 ...
- list转json tree的工具类
package com.glodon.safety.contingency.job; import com.alibaba.fastjson.JSON; import com.alibaba.fast ...