### 学习《C++ Primer》- 6
Part 6: 拷贝控制(第13章)
// @author: gr
// @date: 2015-01-08
// @email: forgerui@gmail.com
一、拷贝、赋值与销毁
拷贝构造函数
拷贝赋值运算符
析构函数
析构函数自身并不直接销毁成员,而是在析构函数之后隐含的析构阶段中被销毁的。使用
=default修饰成员时,要求编译器生成合成的版本。阻止拷贝
在新标准下,使用删除的函数来阻止拷贝,虽然声明了它,但不能以任何方式使用它。struct NoCopy{
NoCopy() = delete;
Nocopy(const NoCopy&) = delete; //阻止拷贝
NoCopy& operator=(const Nocopy&) = delete; //阻止赋值
};
将拷贝函数声明为
private,阻止调用,并且只声明不实现。
二、拷贝控制和资源管理
一旦一个类需要析构函数,那么它几乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。
定义拷贝操作,使类的行为看起来像一个值或指针。像值的类拥有自己的状态,像指针的类则共享状态(当拷贝一个这种类的对象时,副本和原对象使用相同的底层数据)。
行为像值的类,有两个数据成员
string、intclass HasPtr{
public:
HasPtr(const std::string &s = std::string()) : ps(new string(s)), i(0){}
HasPtr(const HasPtr& p) : ps(new string(*p.ps)), i(p.i){}
HasPtr& operator= (const HasPtr&);
~HasPtr(){ delete ps;}
private:
int i;
std::string* ps;
};
HasPtr& HasPtr::operator=(const HasPtr& rhs){
std::string* newp = new std::string(*rhs.ps); //先创建一个新拷贝之后再delete,否则如果传入自己,delete会使rhs.ps失效,再拷贝就什么也没有了。
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
注意:
- 如果一个对象赋予它自身,赋值运算符必须能正确工作。
- 大多数赋值运算符组合了析构函数和构造函数的工作。
行为像指针的类,多个对象共享一份资源,需要使用引用计数。可以用
shared_ptr,也可以自己实现。class HasPtr{
public:
HasPtr(const std::string& s = std::string()) : ps(new string(s)), use(new std::size_t(1)), i(0){}
HasPtr(const HasPtr& rhs) : ps(rhs.ps), use(rhs.use), i(rhs.i){ ++*use; }
HasPtr& operator=(const HasPtr&);
~HasPtr();
private:
int i;
string* ps;
std::size_t* use;
};
//析构函数
HasPtr::~HasPtr(){
if (--*use == 0){
delete ps;
delete use;
}
}
//拷贝运算符
HasPtr& HasPtr::operator=(const HasPtr& rhs){
++*rhs.use;
//先判断是否是最后一个拥有资源的类,如果是,则删除资源
if (--*use){
delete ps;
delete use;
}
ps = rhs.ps;
use = rhs.use;
i = rhs.i;
return *this;
}
三、交换操作SWAP
swap函数应该调用swap,而不是std::swap
定义类自己的swap函数,如下:class HasPtr{
public:
friend void swap(HasPtr& lhs, HasPtr& rhs);
};
inline void swap(HasPtr& lhs, HasPtr& rhs){
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
}
每个
swap函数都应该是未加限制的,这样如果存在类型特定的swap版本,则优先于特定的swap版本。在赋值中使用
swapHasPtr& HasPtr::operator=(HasPtr rhs){
//参数是一个值,而不是引用
swap(*this, rhs);
return *this;
}
四、对象移动
新标准中的一个特性是可以移动而非拷贝对象的能力。这样会大幅度提升性能。
为了支持移动操作,新标准引入了一种新的引用类型,右值引用,就是必须绑定到右值的引用。通过
&&而不是&来获得引用。
int &&rr = i * 42;
int &&rr1 = 42; //正确:字面常量是右值
int &&rr2 = rr1; //错误:变量是左值标准
move函数
可以通过move显式地将右值引用绑定到一个左值上。int &&rr3 = std::move(rr1);
移动构造函数和移动赋值运算符
移动构造函数的参数是该类类型的一个右值引用。StrVec::StrVec(StrVec &&s) noexcept //移动操作不应抛出任何异常
: elements(s.elements), first_free(s.first_free), cap(s.cap)
{
//令s进入这样的状态,对其运行析构函数是安全的
s.elements = s.first_free = s.cap = nullptr; //置为nullptr后,便可析构rhs,不会影响
} //移动赋值运算符
StrVec& StrVec::operator=(StrVec&& rhs) noexcept{
if (this != &rhs){
free();
elements = rhs.elements;
first_free = rhs.first_free;
cap = rhs.cap;
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
### 学习《C++ Primer》- 6的更多相关文章
- 学习C++ Primer 的个人理解(一)
<C++ Primer>这本书可以说是公认的学习C++最好的书,但我觉得不是特别适合作为教材,书中内容的顺序让人有些蛋疼.我个人认为初学此书是不能跳着看的.如果急于上手的话,我更推荐< ...
- 学习C++ Primer 的个人理解(九)
这一章介绍顺序容器,在之前的第三章中,了解到的vector就属于顺序容器的一种. 一个容器就是一些特定类型对象的集合. 除了vector,还有哪些顺序容器? vector: 大小可变,随机访问的速度很 ...
- 学习C++ Primer 的个人理解(三)
第三章,主要内容是字符串和数组.感觉作者的意图是希望读者可以早一点可以写出简单的小程序,并且可以早点接触迭代器这种思想. 在我看来,这种内容的难度并不大. 对于编程来说,最重要的应该是思想,类似vec ...
- 学习C++ Primer 的个人理解(二)
本身就一定基础的读者我想变量常量这些概念应该已经不是问题了.但是本章还是有几个重点,需要特别留意一下的: 1.初始化和赋值是不同的操作 2.任何非0值都是true 3.使用新标准列表初始化,在有丢失精 ...
- 学习C++.Primer.Plus 11 使用类
1.操作符重载 重载操作符的几个限制: a) 重载的至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载操作符. b) 不能违反操作符原有来的句法规则. c) ...
- 学习C++.Primer.Plus 10 对象和类
1.类的声明和定义 类的声明和定义. 类声明的格式如下: class className { private://private 是类对象的默认访问控制,因此,可以省略 data member del ...
- 学习C++.Primer.Plus 8 函数探幽
1. 内联函数 普通函数调用: 存储调用指令的地址->将函数参数复制到堆栈->跳到函数地址执行代码(返回值放到寄存器)->跳回调用指令处 2. 当代码执行时间很短,且会被大量调用的 ...
- 学习C++.Primer.Plus 7 函数
C++的返回值类型不能是数组 函数原型中的变量名相当于点位符,因此不要求提供变量名. void cheers(int); C++中不指定参数列表时就使用活力号: void saybye(...); 通 ...
- 学习C++.Primer.Plus 6 分支语句和逻辑操作符
||. &&操作符是一个顺序点 < 操作符从左向右结合 ; < age < )//17<age为true, = 1,肯定 < 27.所以为整个条件为tru ...
- 学习C++.Primer.Plus 5 循环和关系表达式
C++将赋值表达式的值定义为左侧成员的值 赋值操作符是自右向左结合的 cout.setf(ios:: boolalpha);//调用设置标记,命令cout输出true或false,而非1或0. 任何表 ...
随机推荐
- define定义方法
define('a',function(){ ; return x; }); define('b',['a'],function(a){ ; return y+a; }); M.use(['b'],f ...
- java中几个特殊的类
1 内部类-----是在其他类中的类 1.1 内部类的定义 将类定义为 class xxxx{... ...}置于某个类的内部即可‘ 内部类编译时生成 xxxx$xxxx的class文件 内部类不能够 ...
- 关于iOS中SQLITE句柄的使用的细节
1.设计思想:给SQLITE进行封装,利用定义的类别实现方法的简洁,以便达到低耦合效果 控制器代码: #import "ViewController.h" #import &quo ...
- maven依赖传递关系
一.maven 依赖传递规则 举个例子,比如A依赖B,B依赖C,那么A也是依赖C的.A是对B的直接依赖,A对C是传递依赖 ①.最短路劲原则 如,路劲一:A依赖B,B依赖C,C依赖D(1.0.0): 路 ...
- 从零开始学android开发-查看sqlite数据库
C:\Users\Administrator>cd E:\ProSoft\adt-bundle-windows-x86-20140321\sdk\platform-tools
- unity3D中协程和线程混合
这是我google unity3D一个问题偶然发现的在stackflow上非常有趣的帖子: 大意是 要在unity3D上从server下载一个zip,并解压到持久化地址.并将其载入到内存中.以下展示了 ...
- iOS开发——OC篇&协议篇/NSCoder/NSCoding/NSCoping
协议篇/NSCoder/NSCoding/NSCoping 协议声明类需要实现的的方法,为不同的类提供公用方法,一个类可以有多个协议,但只能有一个父类,即单继承.它类似java中的接口. 正式协议(f ...
- android学习日记04--开发中的通用细节
1.android中的计量单位 px (pixels)(像素):是屏幕的物理像素点,与密度相关,密度大了,单位面积上的px会比较多.通常不推荐使用这个 pt(磅):1/72英寸,也较少用 in(英寸) ...
- tcp_tw_recycle和tcp_timestamps的文章汇总
临近年关,人会变得浮躁,期间写的代码可谓乱七八糟.不过出来混始终是要还的,这不最近就发现一个PHP脚本时常连不上服务器. 遇到这类问题,我习惯于先用strace命令跟踪了一下看看: shell ...
- [Effective C++ --026]尽可能延后变量定义式的出现时间
引言 每一次构造和析构都需要成本,因此我们在设计代码的时候,应该尽可能考虑到构造和析构的成本. 第一节 延后实现 考虑有以下的代码: void encrypt(string& s); stri ...