首先,先给大家提个醒。在网上的随书源代码里关于hasptr类的类指针版本编写的移动构造函数、移动赋值运算符、和析构函数部分是有错误的。大家可以把hasptr累指针版本(里面带移动构造函数和移动赋值运算符部分)可以运行下,g++编译环境发现core dumped错误。原因是:在代码中途有的对象的use和ps所指的内存已经被释放了,所以在析构函数中再次直接释放use和ps就是错的了。正确应该在中途每个地方use和ps被接管后都置为nullptr,然后在析构函数中必须首先判断use和ps是否是nullptr,然后再释放。

************************************************************************************************************************************************

对nullptr指针,执行delete操作会引发错误,g++提示:core dumped

************************************************************************************************************************************************

习题13.1拷贝、赋值和销毁

1.拷贝初始化何时发生:

2.拷贝初始化的限制-----不能隐式使用explicit的构造函数初始化一个类类型。

3.编译器可以绕过拷贝构造函数。

习题13.1.1

练习13

答:拷贝构造函数的名字和类名本身相同,参数类型是自身类型的引用,没有返回值。像这样的构造函数叫做拷贝构造函数。在用同类型的一个对象初始化另一个对象时候使用。

答: 在函数调用中,如果函数参数是非引用类型,要拷贝初始化,所以构造函数的参数必须是引用。如果构造函数自己的参数类型也是非引用,那么就会在拷贝参数的时候调用本身,如此反复,则进入无限循环。

练习13.2

答:StrBlob只有一个数据成员,类型是shared_ptr<vector<string>>,如果拷贝一个StrBlob,那么会拷贝shard_ptr对象,原来的shared_ptr的引用计数会增加1。新对象和原来对象会共享内存。

答:

第1处:local = arg。因为用同类型的另一个对象初始化本对象。

第二处:*heap = local;同理

第三处:Point pa[4] = {local,*heap};

当然,这是这段代码所发生的拷贝初始化。

答:

class HasPtr{public:HasPtr(const string & s = string()):ps(new string(s)),i(0){}HasPtr(const HasPtr&);private:string *ps;int i;
};
HasPtr::HasPtr(const HasPtr & p):ps(new string(*p.ps)),i(p.i){}

13.1.2练习

练习13.6

答:

拷贝赋值运算符是定义类类型如何用另一个同类型对象给本对象赋值的运算符。有以下几个特点:

返回类型是自身类型的引用、函数名字是operator=,参数类型是自身引用。

合成的赋值运算符完成用本类类型对象给自身类型对象的赋值任务。

如果编译器没有定义拷贝赋值运算符,那么编译器会合成一个,叫做拷贝赋值运算符。

练习13.7

答:

当用一个StrBlob对象给另一个StrBlob对象赋值的时候,shared_ptr对象对调用自己的拷贝赋值运算符,被赋值shared_ptr对象自身的引用计数会增加1,左侧StrBlob对象的shared_ptr对象的引用计数减1。

类StrBlobPtr对象有一个shared_ptr对象,一个week_ptr<vector<string>>对象和一个size_t对象。赋值的时候,右侧shared_ptr对象的引用计数加1,左侧的shared_ptr的引用计数减1,左侧对象的shared_ptr对象指向右侧的shared_ptr指向的内存。对于weak_ptr对象,赋值后左侧的weak_ptr的内容换成右侧类的weak_ptr的内容。其它不变。curr的值也是赋值。

13.8

答:

class HasPtr{public:HasPtr(const string & s = string()):ps(new string(s)),i(0){}HasPtr(const HasPtr&);HasPtr& operator=(const HasPtr &);~HasPtr(){delete ps;}private:string *ps;int i;
};
HasPtr::HasPtr(const HasPtr & p):ps(new string(*p.ps)),i(p.i){}
HasPtr& HasPtr::operator=(const HasPtr &p){
//这里一定要争取处理自赋值情况,也就是不能再拷贝之前释放掉参数的成员ps指向的内存
string *new_ps = new string(*p.ps);
delete ps;
ps = new_ps;
i = p.i;
return *this;
}

13.1.3析构函数

析构函数执行与构造函数相反的工作:构造函数初始化对象的非static数据成员,还可能做一些其它工作;析构函数释放对象使用的资源,并销毁对象的非static数据成员。

析构函数是类的一个成员函数,名字由波浪号接类名构成。它没有返回值,也不接受参数。析构函数不接受参数,所以不可以重载,一个类只有一个唯一的析构函数。

析构函数完成什么工作

首先执行函数体,然后销毁成员,成员按照初始化的顺序的逆序销毁。

在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。成员销毁时发生什么完全依赖于成员的类型。销毁类类型成员需要执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。

隐式销毁一个内置指针类型的成员不会delete它所指向的对象。

什么时候调用析构函数

无论何时一个对象被销毁,就会自动调用其析构函数:

如下面的例子:

p是一个内置指针,块末尾调用delete,p指向的内存释放。

局部对象p2的引用计数减1,如果引用计数变为0,那么释放对象。

item,vec,块结束后自动销毁,释放资源。

习题13.1.3节练习

练习13.9

答:

13.9

析构函数的名字是波浪号加类名,参数列表为空的一个函数。和析构函数的功能相反,构造函数初始化对象,析构函数负责销毁对象,释放对象占用的资源。

13.10

当一个StrBlob销毁时候,shared_ptr类会自动地释放对象所占用的资源,销毁对象。

StrBlobPtr对象销毁的时候,weak_ptr对象会销毁自己的对象。因为是一种弱引用,所以对原来的智能指针没有任何影响。其它内置类型的成员,则自动销毁。

13.11

class HasPtr{public:HasPtr(const string & s = string()):ps(new string(s)),i(0){}HasPtr(const HasPtr&);~HasPtr(){delete ps;}private:string *ps;int i;
};
HasPtr::HasPtr(const HasPtr & p):ps(new string(*p.ps)),i(p.i){}

13.12

在下面的代码片段中会发生几次析构函数的调用?

是3次,函数执行完毕后销毁局部对象accum,item1,item2共3个局部变量。

13.13

#include <memory>
using namespace std;
struct X{
X(int c):a(c){cout << "X(INT C)" << endl;}
X(){cout << "X()" << endl;}
X(const X&){cout << "X(const &X)" << endl;}
X& operator=(const X&){cout << "X&operator=(const &X)" << endl;return *this;}
~X(){cout << "~X()" << endl;}private:
int a = 0;
int b;
};int main()
{
//直接初始化
X a;
X b(1234);
X c(a);
c  = a;return 0;
}
X()
X(INT C)
X(const &X)
X&operator=(const &X)
~X()
~X()
~X()

13.1.4三/五法则

一、需要析构函数的类也需要拷贝和赋值操作。

需要拷贝操作的类也需要赋值操作,反之亦然。

 

注:我们定义的析构函数会释放动态内存,如不定义自己的析构函数,合成的析构函数不会自动释放动态内存。

这记录了需要拷贝构造函数和拷贝赋值运算符但是不需要析构函数的例子。具体程序如下。

练习题答案

练习13.1.4

答:这是一个典型的应该定义拷贝控制成场合。如果不定义拷贝构造函数和拷贝赋值运算符,依赖程序合成的版本,则在拷贝构造和赋值时,会简单赋值数据成员。对本问题来说,序简单赋值给新对象。

因此,代码中对a、b、c三个对象调用函数,会输出三个相同的序号-————合成拷贝构造函数被调用时简单复制序号,使得三个对象具有相同的序号。

结论:合成的默认拷贝构造函数和拷贝赋值运算符会直接拷贝所有的成员,不会成员的值。

练习13.15

答:在定义的时候,a、b、c获得的mysn的值分别是0、1、2,但调用f的时候,还会进行三次拷贝,第一次拷贝给函数f的变量,调用拷贝构造函数mysn的值继续增加1,变为3,以此类推,调用第二次mysn变为4、5,所以输出结果是3、4、5。

当然这假定第一个元素给值0的情况。

练习13.16.

答:如果f的参数是const Numbered &,那么输出结果变为0,1,原因是如果参数类型传递的是const Numbered &,而不是类型本身,因为传递的是引用类型,所以并不会拷贝对象,所以输出结果是0、1、2。

13.17。

13.14问题中的程序实现如下:

#include <iostream>
#include <memory>
using namespace std;
class Numbered;
void f(Numbered);
class Numbered{
friend void f(Numbered );
private:static int*ptr;int *p;int mysn;
public:Numbered():p(ptr),mysn(*p){++ *p;}void delete_(){delete ptr;}};int * Numbered::ptr = new int(0);
void f(Numbered s){cout << s.mysn << endl;}int main()
{
Numbered a,b = a ,c = b;
f(a);
f(b);
f(c);//调用一个对象来销毁static int *ptr 所指向的空间,当然也把ptr定义为共有的成员,那样就不用定义专门的成员函数了。但破坏了封装的特性。
a.delete_();
return 0;
}

运行结果

0
0
0

14.15题的程序实现:

#include <iostream>
#include <memory>
using namespace std;
class Numbered;
void f(Numbered);
class Numbered{
friend void f(Numbered );
private:static int*ptr;int *p;int mysn;
public:Numbered():p(ptr),mysn(*p){++ *p;}Numbered(const Numbered &s){p = s.p;//从指针中取走内存中的值mysn = *p;++ *p;}void delete_(){delete ptr;}};int * Numbered::ptr = new int(0);
void f(Numbered s){cout << s.mysn << endl;}int main()
{
Numbered a,b = a ,c = b;
f(a);
f(b);
f(c);//调用一个对象来销毁static int *ptr 所指向的空间
a.delete_();
return 0;
}

结果是

3

4

5

13.16实现:

#include <iostream>
#include <memory>
using namespace std;class Numbered;
void f(const Numbered &);class Numbered{friend void f(const Numbered &);private:static int*ptr;int *p;int mysn;
public:Numbered():p(ptr),mysn(*p){++ *p;}Numbered(const Numbered &s){p = s.p;//从指针中取走内存中的值mysn = *p;++ *p;}void delete_(){delete ptr;}};int * Numbered::ptr = new int(0);
void f(const Numbered &s){cout << s.mysn << endl;}int main()
{
Numbered a,b = a ,c = b;
f(a);
f(b);
f(c);//调用一个对象来销毁static int *ptr 所指向的空间
a.delete_();
return 0;
}

结果是0   1   2

习题13.1.4解答完毕。

下面是以上用到程序的自己定义的版本,多定义了拷贝赋值运算符。

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class Numbered{public://本程序需要定义一块与类对象独立的自由空间,用static可以达到这个目的static int * ptr;Numbered():p(ptr),mysn(*p){++ *p;}Numbered(const Numbered & s):p(s.p),mysn(*p){++ *p;}Numbered & operator=(const Numbered &s){p = s.p;mysn = *p;++ *p; //这里不能写成*p ++,否则会先把p增加,然后解引用return *this;}int num()const{return mysn;}private:int *p;int mysn = 0;};
int * Numbered::ptr = new int(0);
int main()
{
Numbered n1;
cout << n1.num() << endl;
Numbered n2 = n1;
cout << n2.num() << endl;
Numbered n3;
cout << n3.num() << endl;
n3 = n1;
cout << n3.num() << endl;
n1 = n3;
cout << n1.num() << endl;
Numbered n4;
cout << n4.num() << endl;
n4 = n2;
cout << n4.num() << endl;
Numbered n5;
cout << n5.num() << endl;
delete Numbered::ptr;return 0;
}
0
1
2
3
4
5
6
7

通过以上习题可以总结出一个结论,类需要使用的,但是不依赖类对象存在的事情,被定义为static是最合适的。这个对象是类使用的,但是和对象无关,有关系。

13.1.5 使用=default

13.1.6 阻止拷贝

//file.ccstruct NoCopy{
NoCopy() = default;
NoCopy(const NoCopy &) = delete;
NoCopy & operator=(const NoCopy &) = delete;
~NoCopy() = delete;private:int a = 0;
};int main()
{
NoCopy n1,n2;  //error:
NoCopy n3 = n1; //error:
n2 = n1;       //error:
return 0;
}

编译后结果:

file.cc: In function ‘int main()’:
file.cc:14:8: error: use of deleted function ‘NoCopy::~NoCopy()’14 | NoCopy n1,n2;|        ^~
file.cc:6:1: note: declared here6 | ~NoCopy() = delete;| ^
file.cc:14:8: error: use of deleted function ‘NoCopy::~NoCopy()’14 | NoCopy n1,n2;|        ^~
file.cc:6:1: note: declared here6 | ~NoCopy() = delete;| ^
file.cc:14:11: error: use of deleted function ‘NoCopy::~NoCopy()’14 | NoCopy n1,n2;|           ^~
file.cc:6:1: note: declared here6 | ~NoCopy() = delete;| ^
file.cc:14:11: error: use of deleted function ‘NoCopy::~NoCopy()’14 | NoCopy n1,n2;|           ^~
file.cc:6:1: note: declared here6 | ~NoCopy() = delete;| ^
file.cc:16:6: error: use of deleted function ‘NoCopy& NoCopy::operator=(const NoCopy&)’16 | n2 = n1;|      ^~
file.cc:5:10: note: declared here5 | NoCopy & operator=(const NoCopy &) = delete;|          ^~~~~~~~

=delete通知读者和编译器,我们不希望定义这些成员。

使用=delete要注意=delete和=default的差别:

1.=delete必须出现在函数第一次声明的时候。一个默认的成员只影响为这个成员而生成的代码,因此,=default直到编译器生成代码的时候才需要。而另一方面,编译器需要直到一个函数是删除的,以便禁止试图使用它的操作。所以必须第一次出现的时候就需要=delete,不然编译器和用户还以为是一般的函数,到处使用。

2.与=default不同之处在于,=delete可以用在任何函数之中(我们只能对编译器合成的默认构造函数或者拷贝构造成员才能使用=default)。虽然删除的函数主要用途是阻止禁止拷贝控制成员,但当我们希望引导函数匹配的过程时,删除函数有时也是有用的。

一、析构函数不能是删除的成员。

一个类或者它的成员的析构函数都不能是删除的,否则这个都无法创建这个类类型的对象。因为这个类型的对象无法删除。下面是c++primer 5th的原文:

二、析构函数是删除的不能定义这个类型的成员,但是可以定义指向这个类型的指针,但是不能释放这些对象。

自己编写一遍试试:

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class NoDtor{
public:NoDtor() = default; //显示使用合成默认析构函数~NoDtor() = delete;//我们不能销毁NoDtor类型的成员
};
int main()
{NoDtor nd;  //错误,析构函数是删除的,不能定义此类型对象NoDtor *p = new NoDtor(); // 正确,析构函数是删除的也可以动态分配这个类型的对象delete p;              //错误,delete类类型对象会调用析构函数,但是析构函数是删除的
return 0;
}

三、合成的拷贝控制成员可能是删除的

1.如果类的某个类类型的成员的析构函数是删除的或者不可访问的(例如private的),那么

这个类的合成的析构函数就是删除的。c++primer 5th原文:

2.如果类的某个成员的拷贝构造函数是删除的或者不可访问的,那么这个类的合成的拷贝构造函数就被定义为删除的;如果一个成员析构函数被定义为删除的或者不可访问的,那么这个类的合成的拷贝构造函数也被定义为删除的。c++primer 5th原文:

3.如果类的某个成员的拷贝赋值运算符被定义为删除的或者不可访问的,或者是类的某个成员是const 或者 引用,那么这个类合成的拷贝赋值运算符就定义为删除的。

class NoDtor{
public:NoDtor() = default;     //显示使用合成默认析构函数//我们不能销毁NoDtor类型的成员
private:int &a;     const int b;
};
int main()
{NoDtor n1;//因为类NoDtor有引用或者const成员,那么默认构造函数被定义为删除的,所以不能创建这个类型对象
NoDtor n2; //error:原因同上
n2 = n1;   //error:有const 或者引用类型(没有合适的初始),拷贝赋值运算符定义为删除的return 0;
}
~  

4.如果类的某个成员的析构函数是删除的或者不可访问的,那么类有一个引用成员,它没有初始化器,或者类有一个const成员,它没有类内初始化器且类型未显式定义默认构造函数,则该类的默认构造函数被定义为删除的。c++primer5th原文:

自己总结如下:

1.析构函数:如果一个类的析构函数是delete的或者不可见的,那么会影响如下:

(1)该类的合成的默认构造函数是删除的。不能定义此类型的对象。即使是调用此类型的合适的构造函数也不行。

(2)如果一个类的某个成员的析构函数是删除的或不可见的,那么这个类的合成的拷贝构造函数就是定义删除的。

其实也好理解,成员的析构函数是删除的或者不可见的,当然也就不能定义那个成员类型了,当然谈不上那个成员的初始化了,那就这个类类型的合成拷贝构造函数就自然被定义为删除的了。按照常理推倒,即时自己定义拷贝构造函数也不能调用去初始化这个类的对象,因为有个成员是不能定义的。

(3)如果一个类的某个成员的析构函数是删除的不可见的,那么这个类的合成的析构函数也是删除的。

提醒:析构函数除了不影响拷贝赋值运算符,其余2个拷贝控制成员都影响,也影响默认构造函数。一共考虑四个函数,default constructor、copy constructor、copy assignment operator、deconstructor,影响了3个。析构函数真是够刁的。回收站很重要呀,和现实世界一样。

2.拷贝构造函数:如果一个类的拷贝构造函数是delete的或者不可见的,那么会影响如下:

如果一个类的某个类类型成员拷贝构造函数是删除的或者不可见的,那么这个类类型的合成的拷贝构造函数是删除的或不可见的。

3.拷贝赋值运算符:如果一个类的拷贝赋值运算符是delete的或者不可见的,那么会影响如下:

如果一个类的某个成员的拷贝赋值运算符是删除的或者不可见的,那么这个类类型的合成的拷贝赋值运算符就是删除的或不可见的。

4.如果一个类有const或者引用成员(引用没有初始化,const没有初始值,或者const的类成员不能默认构造),那么会影响如下事情:

(1)首先这个类的合成的默认构造函数是删除的。

(2)这个类的合成的拷贝赋值运算符被定义为删除的。

也就是说,如果一个类有数据成员不能默认构造、拷贝、赋值或销毁,那么对应的成员函数将被定义为删除的。补充就是析构还会影响拷贝构造和默认构造,以及const或者引用的也影响默认构造和拷贝赋值运算符的合成。

注:给引用赋值以后,左边引用会改变指向对象值,而不会改变本身的值,赋值后,左侧和右侧引用指向不同的对象,这种行为看起来不是我们所期望嗯,所以,对于有引用类型的成员,我们定义为删除的。

四、private拷贝控制。

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class Test{public:Test()= default;~Test()=default;private:Test(const Test &);Test& operator=(const Test &);
};
int main()
{Test t;               //rightTest t1(t);            //wrong:拷贝构造函数是private的t1 = t;                //wrong:拷贝赋值运算符是private的
return 0;
}

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class Test{public:Test()= default;~Test()=default;Test copy(const Test &t){Test t1 = t;      return t;}private:Test(const Test &);Test& operator=(const Test &);
};
int main()
{Test t;t.copy(t);return 0;
}

编译时候错误(链接时候错误)是这样的,如下:

/usr/bin/ld: /tmp/cckYppzr.o: in function `Test::copy(Test const&)':
3.cc:(.text._ZN4Test4copyERKS_[_ZN4Test4copyERKS_]+0x36): undefined reference to `Test::Test(Test const&)'
/usr/bin/ld: 3.cc:(.text._ZN4Test4copyERKS_[_ZN4Test4copyERKS_]+0x49): undefined reference to `Test::Test(Test const&)'
collect2: error: ld returned 1 exit status

13.1.6节练习答案:

练习13.18

答:

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class Employee{private:static int cnt;string name;int number;public:Employee():number(cnt){++ cnt;}Employee(const string &s):name(s),number(cnt){++ cnt;}Employee(const Employee &s):name(s.name),number(cnt){++ cnt;}Employee & operator=(const Employee &s){name = s.name;number = cnt;++ cnt; return *this;   }int num()const{cout << number << endl; return number;}};
int Employee::cnt = 0;
int main()
{
Employee e1;
e1.num();
Employee e2;
e2.num();
Employee e3(e2);
e3.num();
e1 = e3;
e1.num();return 0;
}

执行结果:

0
1
2
3

练习13.19

答案:

需要定义自己的拷贝控制成员。如果不定义的,那么直接拷贝号码了,号码一样了。

练习13.20

答案:

TestQuery有2个数据成员,一个shared_ptr<vector<string>>类型的file,另一个map<string,shared_ptr<vector>>类型的wm成员。

TextQuery,拷贝时候,shared_ptr成员可以直接拷贝指针,左右两边指针都递增引用计数;map类型也是一样,first成员直接拷贝,second成员直接拷贝指针,second成员指向的内存的引用计数增加1。当析构的时候,分别调用2个类型的析构函数,自动释放对象占有的内存。

QueryResult类有3个数据成员,string sought、shared_ptr<set<line_so>> 类型的lines、shared_ptr<vector<string>>的file成员。

拷贝的时候,三个类型分别调用自己的拷贝构造函数,sought会直接拷贝,shared_ptr会直接拷贝指针,右侧lines指向的内存的引用计数增加1,file成员也一样。析构的时候分别调用的析构函数。

练习13.21

答:不需要。理由如下:

两个类虽然都没有定义的拷贝控制成员,但是它们用智能指针管理共享的动态对象(输入文件的内容,查询结果的行号集合),用标准库容器保存大量的容器。而这些标准库机制都有良好的拷贝控制成员,用合成的拷贝控制成员简单地拷贝、赋值、销毁它们,即可保证正确的资源管理。因此,这两个类并不需要定义自己的拷贝控制成员。实际上,这两个类的对象之间就存在资源共享,目前的设计能很好地实现这种共享,同类的对象之间的资源共享也能很自然地解决。

练习13.22

答:

//文件HasPtr.cpp
#include "HasPtr.h"
#include <string>
#include <iostream>
#include <new>
using namespace std;
HasPtr& HasPtr::operator= (const HasPtr& s) {string * new_ps = new string(*s.ps);//释放掉ps原来指向的内存delete ps;i = s.i;ps = new_ps;return *this;
}HasPtr::HasPtr(const HasPtr &s):ps(new string(*s.ps)),i(s.i){}HasPtr::~HasPtr(){   delete ps;  }
//文件HasPtr.h
#pragma once
#include <string>
#include <new>
#include <iostream>
using namespace std;
class HasPtr
{
public:HasPtr(const string& s = string()):ps(new string(s)),i(0) {}HasPtr& operator= (const HasPtr& h);HasPtr(const HasPtr&);~HasPtr();
private:string* ps;int i;
};
//文件main.cpp
#include <string>
#include <iostream>
#include <new>
#include "HasPtr.h"
using namespace std;
int main()
{return 0;
}

练习13.23

答:差异就是自己没考虑到自复制时候的情况。自己的代码没考虑到自复制,无法处理自赋值。

练习13.24

答:如果没定义析构函数,那么程序结束的时候会发生内存泄漏,如果没有定义拷贝构造函数,那么系统会使用默认拷贝构造函数,会直接拷贝指针。

练习13.25

答:

//main.cpp
#include <string>
#include <iostream>
#include <vector>
#include <new>
#include <memory>
#include "StrBlob.h"
using namespace std;
int main()
{
StrBlob s1({"st1","st2","st3","st4"});
cout << s1.back() <<  endl;
StrBlob s2 = s1;
s1.pop_back();
cout << s1.size() << endl;
cout << s2.size() << endl;
cout << s1.back() << endl;
cout << s2.back() <<endl;return 0;
}
//StrBlob.cpp
#include "StrBlob.h"
#include <string>    //string
#include <vector>     //vector
#include <new>       //new
#include <memory>      //memory
#include <stdexcept>   //out_of_range
using namespace std;
StrBlob::StrBlob():data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}StrBlob::StrBlob(const StrBlob& s) : data(make_shared<vector<string>>(*s.data)){ }
StrBlob::~StrBlob() {}
StrBlob& StrBlob::operator=(const StrBlob& s) {data = make_shared<vector<string>>(*s.data);return *this;
}void StrBlob::push_back(const string & s)
{data->push_back(s);
}
void StrBlob::pop_back()
{check(0,"pop_back on empty StrBlob.");data->pop_back();
}string& StrBlob::front()
{return     data->front();
}
string& StrBlob::back()
{check(0,"back on empty StrBlob.");
return  data->back();
}void StrBlob::check(size_type i,const string & msg) const
{if (i >= data->size())throw out_of_range(msg);
}
#pragma once
#include <vector>
#include <string>
#include <new>
#include <memory>
using namespace std;
class StrBlob
{
public:typedef vector<string>::size_type size_type;StrBlob();StrBlob(initializer_list<string> il);StrBlob(const StrBlob&);StrBlob& operator=(const StrBlob&);~StrBlob();size_type size()const { return  data->size(); }bool empty()const { return data->empty(); }//add and delete elementvoid push_back(const string& t);void pop_back();//element accessstring& front();string& back();
private:shared_ptr<vector<string>> data;void check(size_type i,const string & msg)const;};

13.25.

答:拷贝构造函数和拷贝赋值运算符应该为自身分配动态内存,而不是与右侧操作数共享对象。

StrBlob使用的是智能指针,可以用合成析构函数来管理,如果一个StrBlob对象离开其作用域,则会自动调用std::shared_ptr的析构函数,当use_count为0时释放动态分配的内存。

13.26

答:

//StrBlob.h
#include <string>
#include <iostream>
#include <memory>
#include <vector>
#include <new>
using namespace std;
class StrBlob{public:using size_type = vector<string>::size_type;StrBlob();StrBlob(initializer_list<string> il);string &front()const;string &back()const;void pop_back();void push_back(const string &s);//copy constructorStrBlob(const StrBlob &s);//copy assignment operatorStrBlob& operator=(const StrBlob &s);//deconstructor ~StrBlob();private:shared_ptr<vector<string>> data;void check(size_type i,const string & msg)const;
};
//functions.cc
#include <string>
#include <iostream>
#include <memory>
#include <vector>
#include <new>
#include "StrBlob.h"
using namespace std;
StrBlob::StrBlob():data(make_shared<vector<string>>()){}
StrBlob::StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)){}//copy constructor
StrBlob::StrBlob(const StrBlob &s):data(make_shared<vector<string>>(*s.data)){}
//copy assignment operator
StrBlob& StrBlob::operator=(const StrBlob &s)
{data = make_shared<vector<string>>(*s.data);return *this;
}
//deconstructor
StrBlob::~StrBlob(){}string& StrBlob::front()const
{return data->front();
}
string& StrBlob::back()const
{return data->back();
}void StrBlob::pop_back()
{data->pop_back();
}
void StrBlob::push_back(const string &s)
{data->push_back(s);
}void StrBlob::check(size_type i,const string &msg)const
{
if(i >= data -> size())throw out_of_range(msg);
}
#include <string>
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
int main()
{return 0;
}

13.2.2

本节内容主要涉及实现类指针的类

//functions.cc
#include "hasptr.h"
#include <string>
#include <iostream>
#include <memory>
#include <new>
using namespace std;
HasPtr::HasPtr(const string &s):ps(new string(s)),use(new size_t(0)),i(0)
{ //++ is priority than *(*use) ++; //or can be written ++*use
}
HasPtr::HasPtr(const HasPtr &rhs):ps(rhs.ps),i(rhs.i),use(rhs.use)
{
(*use) ++;
}HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
//自己赋值给自己什么都不发生
++ *rhs.use;//递增右侧运算对象的引用计数
if(--*use == 0){delete ps;delete use;}
i = rhs.i;
use = rhs.use;
ps = rhs.ps;return *this;
}HasPtr::~HasPtr()
{
//析构函数会递减引用计数,如果引用计数变为0,那么销毁该对象以及该对象所占用的资源
if(--*use == 0){delete ps;delete use;}
}//hasptr.h
#include <string>
#include <iostream>
#include <memory>
#include <new>
#include <vector>
using namespace std;
class HasPtr{public:HasPtr(const string &s = string());HasPtr(const HasPtr &rhs);HasPtr& operator=(const HasPtr &rhs);~HasPtr();size_t use_count()const{return *use;}private:string *ps;int i;size_t *use;
};//main.cc
#include <string>
#include <iostream>
#include <memory>
#include <vector>
#include <new>
#include "hasptr.h"
using namespace std;
int main()
{HasPtr h1;cout << (h1.use_count()) << endl;HasPtr h2 = h1;cout << h1.use_count() << endl;cout << h2.use_count() << endl;HasPtr h3;cout << h3.use_count() << endl;h3 = h2;h2 = h2;cout << h3.use_count() << endl;cout << h2.use_count() << endl;cout << h1.use_count() << endl;return 0;
}

这里主要有这么几个问题,

首先:引用计数应该用动态对象去记录。

其次:类指针的类在几个构造函数或者析构函数的设计注意如下问题:

1.拷贝构造函数:

对于引用计数,先从右侧对象拷贝过来,然后要递增引用计数。其余成员逐个拷贝。就相当于:

先把所有成员都拷贝过来(包括动态内存成员),然后再把动态成员中的引用计数自增即可。

2.拷贝赋值运算符:

要正确处理自赋值情况,先递增右侧引用计数,再递减左侧引用计数。拷贝赋值运算符进行构造函数和析构函数的双重任务,在左侧引用计数变为0的情况下,执行析构函数部分的工作(销毁左侧对象)。

3.析构函数:

析构函数递减引用计数,如果左侧的引用计数变为0,那么销毁对象。

13.2.2节练习

练习13.27:定义你自己的引用计数版本的HasPtr。

答:略

练习13.28

答:略

13.3节

13.29

答:解释swap(HasPtr &,HasPtr &)中对swap的调用会不会导致递归循环。

在此swap函数中又调用了swap来交换HasPtr成员ps和i。但这两个成员的类型分别为指针和整型,都是内置类型,因此函数中的swap调用被解析为std::swap,而不HasPtr的特定版本的swap(也就是自身),所以不会导致递归循环。

练习13.30

为你的类值版本的HasPtr编写swap函数,并测试它。为你的swap函数添加一个打印语句,指出函数什么时候执行。

答:里面有三个文件,即是答案

//main.cc
#include <string>
#include <new>
#include <iostream>
#include "hasptr.h"
using namespace std;
int main()
{
HasPtr h1("hello,china"),h2("hello,world!");
swap(h1,h2);return 0;
}//functions.cc
#include "hasptr.h"
#include <string>
#include <iostream>
#include <memory>
using namespace std;
HasPtr::HasPtr(const string &s):ps(new string(s)){}
HasPtr::HasPtr(const HasPtr &s):ps(new string(*s.ps)),i(s.i){}
HasPtr& HasPtr::operator=(const HasPtr &s){
string *new_ps = new string(*s.ps);
delete ps;
ps = new_ps;
i  = s.i;return *this;
}
HasPtr::~HasPtr()
{delete ps;
}void swap(HasPtr & lhs,HasPtr & rhs)
{
using std::swap;
swap(lhs.ps,rhs.ps);
swap(lhs.i ,rhs.i);
cout << "swap(HasPtr &,HasPtr & )" << endl;
}//hasptr.h
#include <string>
#include <iostream>
#include <new>
using namespace std;
//类值版本
class HasPtr{public:friend void swap(HasPtr &,HasPtr &);HasPtr(const string &s  = string());HasPtr(const HasPtr &);HasPtr& operator=(const HasPtr &);~HasPtr();size_t use_count()const;private:string *ps;int i;
};void swap(HasPtr &,HasPtr &);

运行结果:

swap(HasPtr &,HasPtr & )

13.31

解:

//main.cc
#include <string>
#include <new>
#include <iostream>
#include "hasptr.h"
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
HasPtr h1("hello,china"),h2("hello,world!"),h3("hello,award..");
HasPtr h4("bc"),h5("cde"),h6("fgh");
HasPtr h7("efasdf"),h8("hiasdff"),h9("ijiasdf");
HasPtr h10("jsdf"),h11("kjk"),h12("lsdff");
HasPtr h13("msdf"),h14("nsdf"),h15("osdf");
HasPtr h16("psdf"),h17("qasf"),h18("rsfd");swap(h1,h2);
vector<HasPtr> vs;
vs.push_back(h1);
vs.push_back(h2);
vs.push_back(h3);
vs.push_back(h4);
vs.push_back(h5);
vs.push_back(h6);
vs.push_back(h7);
vs.push_back(h8);
vs.push_back(h9);
vs.push_back(h10);
vs.push_back(h11);
vs.push_back(h12);
vs.push_back(h13);
vs.push_back(h14);
vs.push_back(h15);
vs.push_back(h16);
vs.push_back(h17);
vs.push_back(h18);
sort(vs.begin(),vs.end());
for(auto i : vs)cout << i.str() << endl;return 0;
}//functions.cc
#include "hasptr.h"
#include <string>
#include <iostream>
#include <memory>
using namespace std;
HasPtr::HasPtr(const string &s):ps(new string(s)),i(0){}
HasPtr::HasPtr(const HasPtr &s):ps(new string(*s.ps)),i(s.i){}
HasPtr& HasPtr::operator=(const HasPtr &s){
string *new_ps = new string(*s.ps);
delete ps;
ps = new_ps;
i  = s.i;return *this;
}
HasPtr::~HasPtr()
{delete ps;
}void swap(HasPtr & lhs,HasPtr & rhs)
{
using std::swap;
swap(lhs.ps,rhs.ps);
swap(lhs.i ,rhs.i);
cout << "swap(HasPtr &,HasPtr & )" << endl;
}bool HasPtr::operator<(const HasPtr &rhs)const
{
cout << "opeartor<" << endl;
return (*ps < *rhs.ps);
}//hasptr.h
#include <string>
#include <iostream>
#include <new>
using namespace std;
//类值版本
class HasPtr{public:friend void swap(HasPtr &,HasPtr &);HasPtr(const string &s  = string());HasPtr(const HasPtr &);HasPtr& operator=(const HasPtr &);bool operator<(const HasPtr &)const;string str()const{return *ps;}~HasPtr();size_t use_count()const;private:string *ps;int i;
};void swap(HasPtr &,HasPtr &);

运行结果:

swap(HasPtr &,HasPtr & )
opeartor<
opeartor<
swap(HasPtr &,HasPtr & )
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
opeartor<
bc
cde
efasdf
fgh
hello,award..
hello,china
hello,world!
hiasdff
ijiasdf
jsdf
kjk
lsdff
msdf
nsdf
osdf
psdf
qasf
rsfd

说明,一开始弄个3个元素(vector的元素数),发现结果没有调用swap,后来是18个元素,发现调用了一此swap。说明当元素数量比较少的时候,sort用的是其它方式,而元素数量比较多的时候,用的swap交换元素。

13.32

答:默认的swap版本交换两个对象的非静态成员,对HasPtr而言,就是交换string 的指针ps,引用计数指针use和整型数值i。可以看出,这种语意是符合期望的——两个HasPtr指向了原来对方的string,两者互换string后,各自的引用计数本应该是不变的(都是减1后加1)。因此,默认swap版本已经能正确处理类指针HasPtr的交换,专用swap版本不会带来更多的收益。

//strvec.cc#include <string>
#include <iostream>
#include <memory>
#include "strvec.h"
using namespace std;//一个类对象是const的,它的元素不是const的
StrVec::StrVec(const StrVec &rhs)
{
//allocate_n_copy:可以开辟新的存储空间,然后把给定参数范围的元素逐个拷贝进去,然后
//返回构造好的空间的首元素地址和尾后元素的地址
pair<string*, string*> data = allocate_n_copy(rhs.elements,rhs.first_free);
elements = data.first;
first_free  = cap = data.second;
}
StrVec& StrVec::operator=(const StrVec & rhs)
{auto data = allocate_n_copy(rhs.begin(),rhs.end());free();             //拷贝赋值运算符比拷贝构造函数多一个释放左侧对象的空间的函数elements = data.first;first_free = cap  = data.second;return *this;
}
size_t StrVec::capacity()const
{return  cap - elements;
}
size_t StrVec::size()const
{return  first_free - elements;
}void StrVec::free()
{
if(elements){for(string * p = first_free; p != elements;){alloc.destroy(-- p);        }alloc.deallocate(elements,cap - elements);  //cap -elements 可以计算一共有多少空间}
}void StrVec::chk_n_alloc()
{
//如果容器已满,那么重新分配
if(size() == capacity())reallocate();
}pair<string*,string*> StrVec::allocate_n_copy(const string* b,const string * e)
{string * const data = alloc.allocate(e - b);return {data,uninitialized_copy(b,e,data)};}
allocator<string> StrVec::alloc;void StrVec::reallocate_1()
{
size_t new_capacity = size() ? 1 : 2 * size();
auto new_elements = alloc.allocate(new_capacity);
auto new_first_free = uninitialized_copy(elements,first_free,new_elements);
free();
auto new_cap = new_elements + new_capacity;
elements = new_elements;
first_free = new_first_free;
cap = new_cap;
}void StrVec::reallocate()
{auto new_capacity = size() ? 2*size() : 1;
auto new_elements = alloc.allocate(new_capacity);
auto dest = new_elements;
auto elem = elements;
for(int i = 0; i != size() ; ++ i)
{//std::move(),在需要构造的地方也是可以使用的 alloc.construct(dest ++,std::move(*elem++));
}
free();
elements = new_elements;
first_free = dest;
cap = new_elements + new_capacity;}void StrVec::push_back(const string & rhs)
{chk_n_alloc(); //确保有空间容纳新元素alloc.construct(first_free ++,10,'c');}void StrVec::pop_back()
{alloc.destroy(first_free --);
}StrVec::~StrVec()
{free();
}string * StrVec::begin()const
{return elements;
}
string * StrVec::end()const
{return first_free;
}void StrVec::reserve(size_t s)
{
if(s <= size()) return  ;//先移动元素,然后再释放左侧对象空间
auto  const new_elements = alloc.allocate(s);
auto  dest = new_elements;
auto  elem = elements;
for(size_t i = 0; i != size();++ i )    {alloc.construct(dest ++, std::move(*elem ++));}
free();
elements = new_elements;
cap = new_elements + s;
first_free = dest;}void StrVec::resize(size_t t)
{
if(t > capacity())return ;   else if(t < size()){auto new_first_free = first_free;for(size_t i = 0; i != size() - t; ++ i){alloc.destroy(-- new_first_free);}first_free = new_first_free;return;}
else if(t == size()){return ;}
else if(t > size()){auto new_first_free = first_free;for(size_t  i = 0;i != t - size() ; ++ i){alloc.construct(new_first_free ++,"");       }    first_free = new_first_free;  return;}
}void StrVec::print_info()const
{
cout << "capacity:" << capacity() << endl;
cout << "size:" <<size()<< endl;
}

文件main.cc

#include <string>
#include <iostream>
#include <memory>
#include "strvec.h"
using namespace std;
int main()
{
StrVec v;
//cout << v.capacity() << endl;
//cout << v.size() << endl;for(int i = 0;i != 10; ++i){v.push_back(string("abcde"));cout << v.size() << endl;}
cout <<"capacity:" << v.capacity() << endl;
cout <<"size:" <<  v.size() << endl;for(string * p = v.begin(); p != v.end(); ++p)cout << *p << endl;
v.print_info();v.resize(5);
v.resize(5);
v.resize(100);
cout << "big" << endl;
v.print_info();
v.reserve(100);
v.print_info();
cout << "after resize: " << endl;
for(string* p = v.begin(); p != v.end(); ++ p)cout << *p << endl;
v.print_info();return 0;
}
//strvec.h
#include <string>
#include <iostream>
#include <memory>
using namespace std;
class StrVec
{public:StrVec(): //allocator成员进行默认初始化elements(nullptr),first_free(nullptr),cap(nullptr){}StrVec(const StrVec &); //拷贝构造函数StrVec& operator=(const StrVec &);~StrVec();void push_back(const string &);void pop_back();size_t size()const;size_t capacity()const;string *begin()const;string *end()const;//习题13.39void resize(size_t );void reserve(size_t );void shrink_to_fit();void print_info()const;private:static allocator<string> alloc;string *elements;    //指向指向数组首元素的指针string *first_free;  //指向第一个空闲元素的指针string *cap;   //指向数组尾后位置的指针void free();  //销毁元素并且释放内存void reallocate_1(); 获得更多内存并拷贝已有元素void reallocate();pair<string*,string*> allocate_n_copy(const string*,const string*);//开辟空间,拷贝给定范围的元素void chk_n_alloc();};

运行结果:

1
2
3
4
5
6
7
8
9
10
capacity:16
size:10
cccccccccc
cccccccccc
cccccccccc
cccccccccc
cccccccccc
cccccccccc
cccccccccc
cccccccccc
cccccccccc
cccccccccc
capacity:16
size:10
big
capacity:16
size:5
capacity:100
size:5
after resize:
cccccccccc
cccccccccc
cccccccccc
cccccccccc
cccccccccc
capacity:100
size:5

strvec.h中虽然申明了shrink_to_fit,但是这个函数目前实现不了,因为allocator对象分配的空间,目前不能释放一部分;因为释放allocator对象分配的空间需要函数deallocate函数,这个函数形如:

deallocate(p,n);其中p必须是先前调用allocate得到的指针,而且n必须是先前调用allocate时候的空间的大小。

所以只能全部释放呀。不能部分释放。所以shrink_to_fit是不能实现的。

习题13.40自编答案

StrVec::StrVec(initializer_list<string> li)
{
//迭代器可以直接给指针赋值,棒呀,都什么情况可以这样操作呢?
auto data = allocate_n_copy(li.begin(),li.end());
elements = data.first;
first_free = cap = data.second;
}

练习13.42

//functions.cc
#include <string>
#include <iostream>
#include "strblob.h"
#include "strblobptr.h"
#include <vector>
#include "strvec.h"
using namespace std;//strblob.h成员函数实现
StrBlob::StrBlob():data(make_shared<StrVec>()){}
StrBlob::StrBlob(const StrVec &s):data(make_shared<StrVec>(s)){}StrBlob::size_type StrBlob::size()const{return data->size();
}
void StrBlob::push_back(const string &ele)
{data->push_back(ele);
}void StrBlob::pop_back(){data->pop_back();
}
string & StrBlob::front()
{
return data->front();}
string & StrBlob::back()
{
return data->back();
}
const string& StrBlob::front()const{return data->front();
}
const string& StrBlob::back()const{return data->back();
}bool StrBlob::empty()const
{
return data->empty();
}/* following is the member functions of class StrBlobPtr */
StrBlobPtr::StrBlobPtr():curr(0){}
StrBlobPtr::StrBlobPtr(StrBlob b,size_t i):wptr(b.data),curr(i){}shared_ptr<StrVec>
StrBlobPtr::check(size_t i,const string &msg)const{shared_ptr<StrVec> ret = wptr.lock();
//question1:什么情况下,ret会不指向任何对象呢?
if(!ret)throw runtime_error("unbounded StrBlobPtr");
else if(i >= ret->size())throw out_of_range(msg);
elsereturn ret;
}StrBlobPtr StrBlob::begin()const
{
return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end()const
{
return StrBlobPtr(*this,this->size());
}string & StrBlobPtr::deref()const
{
auto ret = check(curr,"out of range");
return  *(ret->begin() + curr);
}StrBlobPtr &StrBlobPtr::incr()
{check(curr,"out of range");++ curr;return *this;
}//strvec.h
StrVec::StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
allocator<string> StrVec::alloc;
string &StrVec::front()const
{
return *elements;}
string & StrVec::back()const
{
return *(first_free - 1);}
string *StrVec::begin()const
{
return elements;
}
string * StrVec::end()const
{
return first_free;
}StrVec::StrVec(const StrVec &s)
{
auto data = allocate_n_copy(s.begin(),s.end());
elements = data.first;
cap = first_free = data.second;
}
StrVec& StrVec::operator=(const StrVec& s)
{
auto data = allocate_n_copy(s.begin(),s.end());
free();
elements = data.first;
cap = first_free = data.second;return *this;
}pair<string*,string*> StrVec::allocate_n_copy(const string* b,const string *e)
{
string * new_elements = alloc.allocate(e - b);
string * dest = new_elements;
const string * elem = b;
while(elem != e){alloc.construct(dest ++,std::move(*elem ++)); }return {new_elements,dest};
}void StrVec::free()
{
auto elem = elements;
while(elem != first_free){alloc.destroy(elem ++);}
alloc.deallocate(elements,cap - elements);
}
size_t StrVec::size()const
{
return (first_free - elements);}
void StrVec::push_back(const string &s)
{
if(first_free == cap)reallocate();
alloc.construct(first_free ++ ,s);
}
void StrVec::pop_back()
{
check(0,"pop on empty StrVec.");
alloc.destroy(--first_free);
}
bool StrVec::empty()const
{
if(!elements)return true;
elsereturn false;}
StrVec::~StrVec()
{
free();
}void StrVec::reallocate()
{
size_t new_size = size()? 2*size():1;
auto new_elements = alloc.allocate(new_size);
auto new_first_free = uninitialized_copy(elements,first_free,new_elements);
free();
elements = new_elements;
first_free = new_first_free;
cap = new_elements + new_size;
}void StrVec::check(size_t i,const string &msg)const
{
if(i >= size())     throw out_of_range(msg);
}bool StrBlobPtr::operator!=(const StrBlobPtr &s)
{auto p = this->wptr.lock();auto q = s.wptr.lock();if(!(p->elements == q->elements && p->first_free == q->first_free && p->cap == q->cap))return true;elsereturn false;
}
//main.cc#include <string>
#include <iostream>
#include "strblob.h"
#include "strblobptr.h"
#include <vector>
#include "strvec.h"
using namespace std;
int main()
{
StrVec s1;
s1.push_back("abc");
s1.push_back("def");
s1.push_back("fgh");
s1.push_back("qhk");
//for(string* ptr = s1.begin(); ptr != s1.end() ;++ ptr)
//  cout << *ptr << endl;
for(auto p = s1.begin(); p != s1.end() ; ++p)cout << *p << endl;StrBlob str(s1);
StrBlobPtr pk(str);
pk.incr();cout << "deref :" << endl;
cout << pk.deref() << endl;
cout << "end ." << endl;
cout << str.size() << endl;
cout << str.empty() << endl;
cout << str.front() << endl;
cout << str.back() << endl;return 0;
}
//strblob.h
#ifndef STRBLOB_H
#define STRBLOB_H#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include "strvec.h"
using namespace std;
class StrBlobPtr;
class StrBlob{public:friend class StrBlobPtr;StrBlob();StrBlob(const StrVec &l);using size_type = vector<string>::size_type;size_type size()const;void push_back(const string &ele);void pop_back();string &front();string &back();const string &front()const;const string &back()const;bool empty()const;StrBlobPtr begin()const;StrBlobPtr end()const;private:shared_ptr<StrVec> data;void check(size_type i,const string &msg);};#endif
//strblobptr.h#ifndef INCLUDE_H_STRBLOBPTR
#define INCLUDE_H_STRBLOBPTR#include <string>
#include <iostream>
#include <memory>
#include <vector>
#include "strvec.h"
using namespace std;
class StrBlobPtr{public:
StrBlobPtr();
StrBlobPtr(StrBlob b,size_t curr = 0);
string & deref()const;
StrBlobPtr & incr();
bool operator!=(const StrBlobPtr &s);private:weak_ptr<StrVec> wptr;size_t curr;shared_ptr<StrVec>  check(size_t i,const string &msg)const;
};#endif
//strvec.h#ifndef STRVEC_H
#define STRVEC_H#include <string>
#include <iostream>
#include <memory>
using namespace std;
class StrVec
{friend class StrBlobPtr;public:StrVec();StrVec(const StrVec &);StrVec& operator=(const StrVec &);~StrVec();string & front()const;string & back()const;string * begin()const;string * end()const;void pop_back();void push_back(const string &s);size_t size()const;bool   empty()const;void reallocate();private:static allocator<string> alloc;string * elements;string * first_free;string * cap;pair<string*,string*> allocate_n_copy(const string *,const string *);void free();//主要检查是否i已经达到容器的尾后位置void check(size_t i,const string &msg)const;
};#endif

在g++编译器中执行:

g++ main.cc functions.cc -o exe
./exe

得到如下结果:

abc
def
fgh
qhk
deref :
def
end .
4
0
abc
qhk

结果,目前来来,全部通过编译。

13.43更倾向于for_each算法,因为不用for去迭代。

13.44

//string.h
#ifndef STRING_H_INCLUDE
#define STRING_H_INCLUDE#include <utility>
#include <string>
#include <memory>
using namespace std;
class String
{public:String();char * begin()const;char * end()const;char front()const;char back()const;size_t size()const;bool   empty()const;void push_back(char ch);void pop_back();size_t capacity()const;    private:void check(size_t ,const string &msg)const;pair<char*,char*>allocate_n_copy(char* ,char *);void free();char * elements;char * first_free;char * cap;void reallocate();static allocator<char> alloc;};#endif
//functions.cc#include <iostream>
#include <string>
#include "string.h"
#include <memory>
#include <algorithm>
using namespace std;String::String():elements(nullptr),first_free(nullptr),cap(nullptr){}
allocator<char> String::alloc;
char* String::begin()const
{return elements;
}char* String::end()const
{return first_free;
}void String::check(size_t i,const string &msg)const
{
if(i >= size())throw out_of_range(msg);
}size_t String::size()const
{
return first_free - elements;
}
bool String::empty()const
{
if(size() == 0)return true;
elsereturn false;
}
void String::push_back(char ch)
{
if(size() == capacity())reallocate();alloc.construct(first_free ++,ch);
}
void String::pop_back()
{check(0,"pop on empty String.");alloc.destroy(--first_free);
}char String::front()const
{
check(0,"front on empty String.");return *elements;
}char String::back()const
{
check(0,"back on empty String.");
char * ptr = first_free - 1;return *ptr;
}size_t String::capacity()const
{return cap - elements;
}
void String::free()
/*这里一定要注意,考虑元素个数是0个的情况*/
{
if(!elements)return ;
for_each(elements,first_free,[](char ch){alloc.destroy(&ch);});//for_each算法省去了循环
alloc.deallocate(elements,capacity());
}pair<char*,char*>
String::allocate_n_copy(char * b,char * e)
{
auto new_elements = alloc.allocate(e - b);return {new_elements,uninitialized_copy(b,e,new_elements)};
}void String::reallocate()
{
auto new_size = size()?2 *size() : 1;
auto new_elements = alloc.allocate(new_size);
auto new_first_free = uninitialized_copy(elements,first_free,new_elements);
free();
elements = new_elements;
first_free  = new_first_free;
cap = new_size + new_elements;
}
//main.cc
#include <string>
#include <iostream>
#include <memory>
#include  "string.h"
using namespace std;int main()
{
String s;
s.push_back('a');
cout << s.front() << endl;
cout << s.back() << endl;return 0;
}

13.6.2节学习

类似string类(以及其他标准库类),如果我们自己的类能够同时支持移动和拷贝,那么也能从中受益。为了让我们自己的类型同时支持移动和拷贝,那么我们需要定义自己的移动构造函数和移动赋值运算符。这两个成员类似拷贝构造函数和拷贝赋值运算符,但是它们是从给定对象“窃取”资源而不是拷贝资源。

类似拷贝构造函数,移动构造函数的第一个参数也是该类类型的一个引用。不同于拷贝构造函数的是,这个引用参数在移动构造函数中是一个右值引用。与拷贝构造函数一样,任何其他的参数必须有默认实参。

习题13.6.2

习题13.49

类StrVec的移动构造函数和移动赋值运算符:


//(1)接管资源后,要把被接管资源置于安全状态(可析构的状态:容器clear,内置指针nullptr,allocator
//先destroy元素,然后deallocator)
//(2)注意代码书写也不一样
StrVec::StrVec(StrVec && v)noexcept:elements(v.elements),first_free(v.first_free),cap(s.cap)
{
v.elements = v.first_free = v.cap = nullptr;
}
//1.(代码从参数列表开始就注意了)
//2.判断是否是左右操作数是同一个对象,防止自赋值情况
//3.要把被接管或者被移动的对象置于安全状态(可以析构的状态)
StrVec& StrVec::operator=(StrVec && v)noexcept
{if(this != &v){free();elements  = v.elements;first_free = v.first_free;cap = v.cap;v.elements = v.first_free = v.cap = nullptr; }return *this;
}

String.h的

String::String(String && s)noexcept:elements(s.elements),first_free(s.first_free),cap(s.cap)
{s.elements = s.first_free = s.cap = nullptr;
}String &String::operator=(String&& s):noexcept
{
if(this != &s){free();elements = s.elements;first_free = s.first_free;cap = s.cap;s.elements = s.first_free = s.cap = nullptr;}return *this;
}
Message::Message(Message && rhs):contents(std::move(rhs.contents))
{move_Folders(&rhs);
}
Message& Message::operator=(Message && m)
{
if(this != &m){remove_from_Folders();contents = std::move(m.contents);move_Floders(&m);   }
return *this;
}

注意:接管资源后,要把被接管资源置于安全状态(可析构的状态:容器clear,内置指针nullptr,allocator对象先destroy元素,然后deallocator。

hasptr类指针版本

文件hasptr.h

#ifndef HASPTR_H
#define HASPTR_H#include <string>
#include <iostream>
#include <new>
using namespace std;
class HasPtr{public:HasPtr(const string &s  = string());HasPtr(const HasPtr &);HasPtr& operator=(const HasPtr &);HasPtr(HasPtr &&)noexcept;HasPtr& operator=(HasPtr &&)noexcept;~HasPtr();size_t use_count()const{cout <<"use_count:" << *use << endl;return *use;}private:size_t * use;string * ps;int i;
};inline
HasPtr::HasPtr(const std::string &s): ps(new std::string(s)), i(0), use(new std::size_t(1)) {}// copy constructor copies all three data members
// and increments the counter
inline
HasPtr::HasPtr(const HasPtr &p): ps(p.ps), i(p.i), use(p.use) { ++*use; }inline
HasPtr::HasPtr(HasPtr &&p)noexcept: ps(p.ps), i(p.i), use(p.use) { p.ps = 0; p.use = 0; }inline
HasPtr::~HasPtr()
{
//you must charge whether the object is released before
//f.g.it can operate delete use and delete ps,so it is released before,it can't released again at all
if(!use)   //important{return ;}if (--*use == 0) {   // if the reference count goes to 0delete ps;       delete use;      use = nullptr;  //it'll be a minute can determine that whether it is released ps = nullptr;  }
}inline
HasPtr &
HasPtr::operator=(HasPtr &&rhs)noexcept
{//这里先判断是不是自赋值情况,然后才能做赋值运算,如果先做赋值运算,那么释放的对象就不对了if (this != &rhs) {if (--*use == 0) {   // do the work of the destructordelete ps;ps = nullptr;delete use;use = nullptr;//无论什么时候,内置指针释放后,置为nullptr是个不错的主意}ps = rhs.ps;         // do the work of the move constructori = rhs.i;use = rhs.use;    }return *this;
}
inline
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{++*rhs.use;  // increment the use count of the right-hand operandif (--*use == 0) {   // then decrement this object's counterdelete ps;ps = nullptr; // if no other users delete use; use = nullptr;    // free this object's allocated members}ps = rhs.ps;         // copy data from rhs into this objecti = rhs.i;use = rhs.use; return *this;        // return this object
}#endif

0和nullptr是一回事。作用一样。

hasptr类值版本:

#ifndef HASPTR_H
#define HASPTR_H#include <string>
#include <iostream>
#include <new>
using namespace std;
/**  注意:当你释放了某个对象的空间后,余下的部分可以交给编译器去做。但是不能让编译器再使用*  这个对象的被释放的值,否则会引发未定义的行为。比如:Segment fault,core dumped.*  比如: 执行h1 = h2; 之后,h1的资源就被拷贝赋值运算符释放掉了,此时h1(占用内存空间的值)不能再使用  *  了,最后由编译器释放就可以了.*/
class HasPtr{public:HasPtr(const string &s  = string());HasPtr(const HasPtr &);HasPtr& operator=(const HasPtr &);HasPtr(HasPtr &&)noexcept;HasPtr& operator=(HasPtr &&)noexcept;~HasPtr();private:string * ps;int i;
};
inline
HasPtr::HasPtr(const std::string &s): ps(new std::string(s)), i(0) {}// copy constructor copies all three data members
// and increments the counter
inline
HasPtr::HasPtr(const HasPtr &p): ps(new string(*p.ps)), i(p.i) {}inline
HasPtr::HasPtr(HasPtr &&p)noexcept: ps(p.ps), i(p.i){ p.ps = 0;}
inline
HasPtr::~HasPtr()
{
//you must charge whether the object is released before
//f.g.it can operate delete use and delete ps,so it is released before,it can't released again at allif(!ps)delete ps;
}inline
HasPtr &
HasPtr::operator=(HasPtr &&rhs)noexcept
{//这里先判断是不是自赋值情况,然后才能做赋值运算,如果先做赋值运算,那么释放的对象就不对了if(this != &rhs){delete ps;ps = rhs.ps;i = rhs.i;rhs.ps = nullptr;     }   return *this;
}
inline
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{string *new_ps = new string(*rhs.ps);delete ps;ps = nullptr;   //这个很重要,提现代码的严谨性,一会析构函数需要检测,检测到nullptr,不用再释放了,否则引发未定义行为ps = new_ps;i = rhs.i;return *this;        // return this object
}#endif

13.6.3节练习

13.55

void StrBlob::push_back(string &&rhs)
{
data->push_back(std::move(rhs));//这里必须加std::move(),因为rhs是一个右值,但是是一个变量,
//rhs本质上是一个左值。所以要加std::move()
}

13.56

会导致无限循环(每次调用自己)。

13.57

Foo Foo::sorted()const &{return Foo(*this).sorted();}
//Foo(*this)是构造的一个临时Foo对象,所以成功调用右值版本的成员函数
#include <string>
#include <iostream>
#include <new>
using namespace std;
class Foo
{private:int a = 0;public:Foo() = default;Foo(int b):a(b){}void sort()&&{cout << "call sort() &&" <<endl;}void sort()const &{Foo ret(*this);cout << "call sort()const&" << endl;return Foo(*this).sort();         }};int main()
{
Foo o1,o2,o3;
o1.sort();
(Foo(o1)).sort();return 0;
}
call sort()const&  //o1.sort()调用左值版本,但是左值版本会调用右值版本所以有2个输出
call sort() &&     //也是左值版本的输出
call sort() &&    //(Foo(o1)).sort()因为这个是调用右值版本,所以有一个输出

以下是自己先前写的一些代码,绝大部分都有问题。但是毕竟是自己亲力所为,所以就不删除了。

13.2节练习

练习13.22

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class HasPtr{public:
//这个构造函数指定默认初始值,s为 string()HasPtr(const string &s = string()):ps(new string(s)),i(0){cout << "call 默认构造函数"  << endl;}HasPtr(const HasPtr&s):ps(new string(*s.ps)),i(s.i){cout << "call 拷贝构造函数" << endl;}HasPtr&operator=(const HasPtr& s){i = s.i;//先拷贝底层string//这里有错误,ps原来指向的内存没有释放掉,内存泄漏.ps = new string(*s.ps);cout << "call 拷贝赋值运算符"<< endl; return *this;}~HasPtr(){delete ps;cout << "call 析构函数"  <<endl;}string&operator*(){return *ps;}HasPtr&operator=(const string &s){*ps = s;return *this;}private:int i;string *ps;
};int main()
{
HasPtr h1("good2");
HasPtr h2("good1");
h2 = h1;
h1 = h1;
h2 = "hello,world";
cout << *h1 << endl;
cout << *h2 << endl;return 0;
}

乍一看,好像好像上面的程序没有问题,其实已经发生严重错误——内存泄露。

正确的拷贝赋值运算符定义如下:


HasPtr&operator=(const HasPtr& s){i = s.i;//先拷贝底层stringstring *new_ps = new string(*s.ps);delete ps;ps = new_ps;cout << "call 拷贝赋值运算符"<< endl; return *this;}

练习13.27答案一:

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class HasPtr{public:HasPtr(const string &s=string()):ps(new string(s)),i(0),use(new size_t(1)){use ++;}//下面++*use,指的是递增use指向的引用计数HasPtr(const HasPtr &p):ps(p.ps),i(p.i),use(p.use){++ *use;}~HasPtr(){if(--*use == 0){delete ps;delete use;}}HasPtr& operator=(const HasPtr &p){++ *p.use;-- *use;if(*use == 0){delete ps;delete use;}ps = p.ps;i = p.i;use = p.use;return *this;}private:string *ps;int i;size_t * use;
};int main()
{return 0;
}

注意:切记不要出现++ use;出现这句,那么会把指针use指向别的单元。也就是执行这句代码后,use已经不是指向原来use的内存空间了。

13.27答案2:

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class HasPtr{public:HasPtr(const string &s=string()):ps(new string(s)),i(0),use(new size_t(1)){cout <<"initialize:"<< "*use" << *use << endl;}HasPtr(const HasPtr &p):ps(p.ps),i(p.i),use(p.use){cout << "before add:"<<*use << endl;++ *use;cout << "copy constructor:" << *use << endl;}~HasPtr(){if(--*use == 0){delete ps;delete use;}}HasPtr& operator=(const HasPtr &p){++ *p.use;-- *use;if(*use == 0){delete ps;delete use;}ps = p.ps;i = p.i;use = p.use;return *this;}HasPtr& operator=(const string &p){//只是把一个新的string对象赋值给本HasPtr,其余什么都不变,注意这个思路的,这个string不能也算一个对象,所以不占用引用计数。*ps = p;return *this;     }string& operator*(){return *ps;        }size_t use_count()const{return *use;}private:string *ps;int i;size_t * use;
};int main()
{
HasPtr p1("good");
HasPtr p2 = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
cout << *p1 << endl;
cout << *p2 << endl;return 0;
}

1.这段代码增加了医院运算符*,用来获取HasPtr的string对象,结果是string&,所以结果是一个左值。

可以利用这句代码改变HasPtr对象内的string的值。

2.增加了string赋值,string赋值,仅仅改变一个HasPtr内的string的值,其余内容一切不改变。

c++ primer 5th第13章拷贝控制知识点和自编习题答案相关推荐

  1. C++ primer 第13章 拷贝控制

    文章目录 前言 拷贝.赋值与销毁 拷贝构造函数 合成拷贝构造函数 拷贝初始化和直接初始化 拷贝初始化的发生: 参数和返回值 拷贝初始化的限制 拷贝赋值运算符 重载赋值运算符 合成拷贝赋值运算符 析构函 ...

  2. C++ primer 第13章 拷贝控制

    文章目录 前言 拷贝.赋值与销毁 拷贝构造函数 合成拷贝构造函数 拷贝初始化和直接初始化 拷贝初始化的发生: 参数和返回值 拷贝初始化的限制 拷贝赋值运算符 重载赋值运算符 合成拷贝赋值运算符 析构函 ...

  3. 《C++ Primer》第13章 13.5节习题答案

    <C++ Primer>第13章 拷贝控制 13.5节 动态内存管理类 习题答案 练习13.39:编写你自己版本的StrVec,包括自己版本的reserve.capacity(参见9.4节 ...

  4. c++primer plus 第13章 编程题第2题

    c++primer plus 第13章 编程题第2题 #pragma once #ifndef CD_H_ #define CD_H_ //base classclass Cd { private:c ...

  5. 《C语言程序设计》第五版谭浩强课后答案 第九章《用户自己建立数据类型​》习题答案 (大一大二、考研、计算机二级必看)

    第九章<用户自己建立数据类型​>习题答案 1.定义一个结构体变量(包括年.月.日).计算该日在本年中是第几天,注意闰年问题. 2.写一个函数days,实现第1 题的计算.由主函数将年.月. ...

  6. C++ Primer 第十三章 拷贝控制

    当定义一个类时,我们显式或隐式指定在此类型的对象执行拷贝,移动,赋值,销毁时做什么,通过拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符和析构函数. 拷贝赋值与销毁 如果构造函数的第一个参数 ...

  7. c++primer 5th第15章基础、课后习题自己解析、心得体会等

    先看一个例子: #include <string> #include <memory> #include <iostream> using namespace st ...

  8. C++ Primer Plus 第13章笔记

    第13章 类继承 13.1 一个简单的基类 class RatedPlayer : public TableTennisPlayer {... } 冒号指出RatedPlayer继承TableTenn ...

  9. 计算机原理简明教程第二章,《计算机原理简明教程》习题答案[参考].doc

    <计算机原理简明教程>习题参考答案 第一章习题答案 1.1 答:是1946年在美国宾夕法尼亚大学诞生,称为ENIAC. 特点是由1800个电子管和1500个继电器组成,重30吨:功耗150 ...

最新文章

  1. bzoj 2588 Spoj 10628. Count on a tree (可持久化线段树)
  2. Java Review - 并发编程_ 信号量Semaphore原理源码剖析
  3. 解决memcached不能远程访问的问题
  4. SlickOne 敏捷开发框架介绍(二) -- 多用户/多租户/SAAS软件基础框架实现
  5. OSX上Docker快速上手-以部署node.js环境为例
  6. boost 获取时间
  7. @Configuration与@Component作为配置类的区别
  8. 计算机专业考注册测绘师经验,2017年注册测绘师考试知识点整理:测绘综合能力--摄影测量与遥感...
  9. python爬虫之数据提取Xpath(爬取起点中文网案例)
  10. 2022新版UI云购H5系统源码+完美运行/功能强大
  11. python内置库和标准库的区别_python标准库和第三方库的区别
  12. C语言 哲学家就餐问题
  13. Label Consistent Matrix Factorization Hashingfor Large-Scale Cross-Modal Similarity Search(LC)--文献翻译
  14. IP地址的组成及简单分类
  15. python 解析pdf矢量图_如何从PDF文件中提取矢量图
  16. python修改ppt的字体和颜色_ppt-页面大小和颜色更改
  17. ab并发测试-Linux
  18. 用计算机怎样搜wifi网,如何用电脑设置wifi?用电脑设置wifi方法介绍
  19. 极品冷幽默,搞笑没得说
  20. requireJs笔记

热门文章

  1. Windows 7 在资源管理器中显示软件快捷方式
  2. 46 关于Linux的I/O重定向
  3. js获取最近几天的日期(转载)
  4. 修改python默认的编码方式
  5. [译] APT分析报告:11.深入了解Zebrocy的Dropper文档(APT28)
  6. [知识图谱实战篇] 三.Python提取JSON数据、HTML+D3构建基本可视化布局
  7. iOS之深入解析内存管理NSTimer的强引用问题
  8. OpenGL ES之GLSL常用内建函数
  9. 2015/Province_Java_A/3/九数分三组
  10. 2014/School_C_C++_A/5/勾股定理