概述
C++ Primer(第五版)|练习题答案与解析(第十三章:拷贝控制)
本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++ Primer
C++ Primer
C++ Primer
练习题13.1
拷贝构造函数是什么?什么时候使用它?
P440。如果一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
P441。
- 使用“=”定义变量时。
- 将一个对象作为实参传递给一个非引用类型的形参。
- 从一个返回类型为非引用类型的函数返回一个对象。
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员。
练习题13.2
解释为什么下面的声明是非法的:
Sales_data::Sales_data(Sales_data rhs);
P440,拷贝构造函数的第一个参数必须是一个引用类型。
练习题13.3
当我们拷贝一个StrBlob时,会发生什么?拷贝一个StrBlobPtr呢?
“StrBlob中元素复制,且智能指针计数加一。StrBlobStr中元素复制,弱指针复制不影响计数器”。
“拷贝StrBlob时,其shared_ptr成员的引用计数会增加。拷贝StrBlobPtr,unique_ptr成员的引用计数不变,其引用了shared_ptr,但不影响shared_ptr的引用计数。”
练习题13.4
假定Point是一个类类型,它有一个public的拷贝构造函数,指出下面程序片段中哪些地方使用了拷贝构造函数。
Point global;
Point foo_bar(Point arg)
// 参数为非引用类型,需拷贝,使用了拷贝构造
{
Point local = arg, *heap = new Point(global); // 使用了拷贝构造
*heap = local;
Point pa[4] = {local, *heap};
// 使用了拷贝构造
return *heap;
// 函数的返回类型非引用,也需要进行拷贝,使用了拷贝构造
}
练习题13.5
给定下面的类框架,编写一个拷贝构造函数,拷贝所有成员。你的构造函数应该动态分配一个新的string,并将对象拷贝到ps指向的位置,而不是ps本身的位置。
class HasPtr {
public:
HasPtr (const std::string &s = std::string()) : ps (new std::string(s)), i(0){}
// 拷贝构造函数
HasPtr(const HasPtr& hp) : ps (new std::string(*hp.ps)), i (hp.i) {}
private:
std::string *ps;
int i;
}
练习题13.6
拷贝赋值运算符是什么?什么时候使用它?合成拷贝赋值运算符完成什么工作?什么时候会生成合成拷贝赋值运算符?
P443,拷贝赋值运算符是重载”=“运算符,即为一个名为operator=的函数,接受一个与其所在类相同类型的参数,在发生赋值操作的时候使用。
P444,合成拷贝赋值运算符将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,返回一个指向其左侧运算对象的引用。当类未定义自己的拷贝赋值运算符,编译器会生成一个合成拷贝运算符。
练习题13.7
当我们将一个StrBlob赋值给另一个StrBlob时,会发生什么?赋值StrBlobPtr呢?
会发生浅拷贝,所有的指针都指向同一块内存。赋值StrBlob时,智能指针所指对象内存相同,shared_ptr的引用计数加1,赋值StrBlobPtr时,弱指针所致对象内存相同,引用计数不变。
练习题13.8
为13.1.1节练习13.5中的HasPtr类编写赋值运算符。类似拷贝构造函数,你的赋值运算符应该将对象拷贝到ps指向的位置。
#include <string>
class HasPtr {
public:
HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }
HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }
HasPtr& operator=(const HasPtr &rhs_hp) {//赋值运算符
if(this != &rhs_hp){
std::string *temp_ps = new std::string(*rhs_hp.ps);
delete ps;
ps = temp_ps;
i = rhs_hp.i;
}
return *this;
}
private:
std::string *ps;
int i;
};
练习题13.9
析构函数是什么?合成析构函数完成什么工作?什么时候会生成合成析构函数?
P444,析构函数执行与构造函数相反的操作,释放对象使用的资源,并销毁对象的非static数据成员。
P446,对于某些类,合成析构函数被用来阻止该类型的对象被销毁。如果不是这种情况,合成析构函数的函数体就为空。
当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。
练习题13.10
当一个StrBlob对象销毁时会发生什么?一个StrBlobPtr对象销毁时呢?
“销毁StrBlob时,分别会执行vector、shared_ptr、string的析构函数,vector析构函数会销毁我们添加到vector中的元素,shared_ptr析构函数会递减StrBlob对象的引用计数。”
练习题13.11
为前面练习中的HasPtr类添加一个析构函数。
#include <string>
class HasPtr {
public:
HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }
HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }
HasPtr& operator=(const HasPtr &hp) {
std::string *new_ps = new std::string(*hp.ps);
delete ps;
ps = new_ps;
i = hp.i;
return *this;
}
~HasPtr() {//析构函数
delete ps;
}
private:
std::string *ps;
int i;
};
练习题13.12
在下面的代码片段中会发生几次析构函数调用?
bool fcn (const Sales_data *trans, Sales_data accum)
{
Sales_data item1 (*trans), item2 (accum);
return item1.isbn() != item2.isbn();
}
退出作用域时,item1和item2会调用析构函数。accum应该也会调用析构函数,trans没有。
P446,当一个对象的引用或指针离开作用域,并不会执行析构。
练习题13.13
理解拷贝控制成员和构造函数的一个好方法时定义一个简单的类,为该类定义这些成员,每个成员都打印出自己的名字:
struct X {
X() { std::cout << “X()” << std::endl; }
X(const X&) { std::cout << “X(const X&)” << std::endl; }
};
给X添加拷贝赋值运算符和析构函数,并编写一个程序以不同方式使用X的对象:将它们作为非引用和引用参数传递;动态分配它们;将它们存放于容器中;诸如此类。观察程序的输出,直到你确认理解了什么时候会使用拷贝控制成员,以及为什么会使用它们。当你观察程序输出时,记住编译器可以略过对拷贝构造函数的调用。
#include <iostream>
#include <vector>
#include <initializer_list>
struct X {
X() { std::cout << "X()" << std::endl; }
X(const X&) { std::cout << "X(const X&)" << std::endl; }
X& operator=(const X&) { std::cout << "X& operator=(const X&)" << std::endl; return *this; }//拷贝赋值预算符
~X() { std::cout << "~X()" << std::endl; }//析构函数
};
void f(const X &rx, X x)//X x这个也要使用构造函数,也会调用析构
{
std::vector<X> vec;
vec.push_back(rx);
vec.push_back(x);
std::cout << "-------离开f1作用域销毁-----" << std::endl;
}
void f2(const X &rx)
{
std::cout << "-------离开f2作用域销毁-----" << std::endl;
}
void f3(X x)//X x这个也要使用构造函数,也会调用析构
{
std::cout << "-------离开f3作用域销毁-----" << std::endl;
}
int main()
{
std::cout << "-------创建-----" << std::endl;
X *px = new X;
X x;
std::cout << "-------函数作用域1-----" << std::endl;
f(*px, *px);
std::cout << "-------函数作用域2-----" << std::endl;
f2(*px);
std::cout << "-------函数作用域3-----" << std::endl;
f3(x);
std::cout << "-------函数作用域1-----" << std::endl;
f(x, x);
std::cout << "-------销毁-----" << std::endl;
delete px;
std::cout << "-------程序结束销毁-----" << std::endl;
return 0;
}
测试:
-------创建-----
X()
X()
-------函数作用域1-----
X(const X&)
X(const X&)
X(const X&)
X(const X&)
~X()
-------离开f1作用域销毁-----
~X()
~X()
~X()
-------函数作用域2-----
-------离开f2作用域销毁-----
-------函数作用域3-----
X(const X&)
-------离开f3作用域销毁-----
~X()
-------函数作用域1-----
X(const X&)
X(const X&)
X(const X&)
X(const X&)
~X()
-------离开f1作用域销毁-----
~X()
~X()
~X()
-------销毁-----
~X()
-------程序结束销毁-----
~X()
练习题13.14
假定numbered是一个类,它有一个默认构造函数,能为每个对象生成一个唯一的序号,保存在名为mysn的数据成员中。假定numbered使用合成的拷贝控制成员,并给定如下函数:
void f (numbered s) { cout << s.mysn << endl; }
则下面代码输出什么内容?
numbered a, b = a, c = b;
f(a); f(b); f©;
会输出三个相同的序号。因为是合成拷贝,都指向同一块内存,所以a, b, c实际使用同一个mysn结构。
练习题13.15
假定numbered定义了一个拷贝构造函数,能生成一个新的序号。这会改变上一题中调用的输出结果吗?如果会改变,为什么?新的输出结果是什么?
会改变,因为调用f函数传参时以传值方式传递,需要调用拷贝构造函数,会生成一个新的序号,输出只有三个不同的序号。(一共生成6个不同的数字)
练习题13.16
如果f中的参数是const numbered&,将会怎样?这会改变输出结果吗?如果会改变,为什么?新的输出结果是什么?
参考13.13,如果传的是引用,则传递时numbered对象不会调用拷贝构造,也不会调用析构。但是使用拷贝,因此a,b,c均不同,因此三个输出不同。
练习题13.17
分别编写前三题中描述的numbered和f,验证你是否正确预测了输出结果。
#include <iostream>
//测试1
class numbered1 {
public:
numbered1() {
mysn = unique++;
}
int mysn;
static int unique;
};
int numbered1::unique = 10;
void f(numbered1 s) {
std::cout << s.mysn << std::endl;
}
//测试2
class numbered2 {
public:
numbered2() {
mysn = unique++;
}
numbered2(const numbered2& n) {
mysn = unique++;
std::cout << "使用拷贝构造" << std::endl;
}
int mysn;
static int unique;
};
int numbered2::unique = 10;
void f(numbered2 s) {
std::cout << s.mysn << std::endl;
}
//测试3
class numbered3 {
public:
numbered3() {
mysn = unique++;
}
numbered3(const numbered3& n) {
mysn = unique++;
std::cout << "使用拷贝构造" << std::endl;
}
int mysn;
static int unique;
};
int numbered3::unique = 10;
void f(const numbered3& s) {
std::cout << s.mysn << std::endl;
}
int main()
{
//测试1
std::cout << "测试1" << std::endl;
numbered1 a1, b1 = a1, c1 = b1;//10
f(a1);f(b1);f(c1);
//测试2
std::cout << "测试2" << std::endl;
numbered2 a2, b2 = a2, c2 = b2;//numbered2(const numbered2& n)
//a2.mysn = 10, b2=a2 -> b2.mysn = 11, c2 = b2 -> c2.mysn = 12;
//unique 是静态变量,拷贝完后不会被释放。
f(a2);f(b2);f(c2);//numbered2 s会使用构造函数,参考13.13
//测试3
std::cout << "测试3" << std::endl;
numbered3 a3, b3 = a3, c3 = b3;
f(a3);f(b3);f(c3);//const numbered3& s不会使用构造函数,参考13.13
}
测试1
10
10
10
测试2
使用拷贝构造
使用拷贝构造
使用拷贝构造
13
使用拷贝构造
14
使用拷贝构造
15
测试3
使用拷贝构造
使用拷贝构造
10
11
12
练习题13.18
定义一个Employee类,它包含雇员的姓名和唯一的雇员证号。为这个类定义默认构造函数,以及接受一个表示雇员姓名的string的构造函数。每个构造函数应该通过递增一个static数据成员来生成一个唯一的证号。
#include <string>
using std::string;
class Employee {
public:
Employee();
Employee(const string &name);
const int id() const { return id_; }
private:
string name_;
int id_;
static int s_increment;
};
int Employee::s_increment = 0;
//默认构造函数
Employee::Employee() {
id_ = s_increment++;
}
//接受一个雇员姓名的构造函数
Employee::Employee(const string &name) {
id_ = s_increment++;
name_ = name;
}
int main()
{
return 0;
}
练习题13.19
你的Employee类需要定义它自己的拷贝控制成员吗?如果需要,为什么?如果不需要,为什么?实现你认为Employee需要的拷贝控制成员。
不需要拷贝控制成员,不存在两个id和name都相同的雇员,因为至少每个雇员的ID都不同。
#include <string>
using std::string;
class Employee {
public:
Employee();
Employee(const string &name);
Employee(const Employee&) = delete;
Employee& operator=(const Employee&) = delete;
const int id() const { return id_; }
private:
string name_;
int id_;
static int s_increment;
};
练习题13.20
你的Employee类需要定义它自己的拷贝控制成员吗?如果需要,为什么?如果不需要,为什么?实现你认为Employee需要的拷贝控制成员。
因为这两个类中使用的是智能指针(shared_ptr
),因此在拷贝时,类的所有成员都将被拷贝,在销毁时所有成员也将被销毁。
练习题13.21
你认为TextQuery和QueryResult类需要定义它们自己版本的拷贝控制成员吗?如果需要,为什么?如果不需要,为什么?实现你认为这两个类需要的拷贝控制操作。
P447,当我们决定一个类是否需要自己版本的拷贝控制成员,一个基本原则是首先确定这个类是否需要一个析构函数。
TextQuery和QueryResult类使用智能指针,可以自动控制释放内存(shared_ptr
),因为其不需要自己版本的析构函数,所以不需要自己版本的拷贝控制函数了。
练习题13.22
假定我们希望HasPtr的行为像一个值。即,对于对象所指向的string成员,每个对象都有一份自己的拷贝。我们将在下一节介绍拷贝控制成员的定义。但是,你以及学习了定义这些成员所需要的所有知识。在继续学习下一节之前,为HasPtr编写拷贝构造函数和拷贝赋值运算符。
#include <string>
class HasPtr {
public:
HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }
HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }
HasPtr& operator=(const HasPtr &hp) {
auto new_p = new std::string(*hp.ps);
delete ps;
ps = new_p;
i = hp.i;
return *this;
}
~HasPtr() {
delete ps;
}
private:
std::string *ps;
int i;
};
练习题13.24
如果本节中的HasPtr版本未定义析构函数,将会发生什么?如果未定义拷贝构造函数,将会发生什么?
“若未定义析构函数,则每次类销毁时都不会释放ps指向内存,造成内存泄漏。”
“如果未定义拷贝构造函数,则如果使用另外一个HasPtr类对象构造新的HasPtr类对象,则两个对象的ps指向同一块内存。如果要销毁这两个对象,就会造成同一块内存被释放两次。”
练习题13.25
假定希望定义StrBlob的类值版本,而且希望继续使用shared_ptr,这样我们的StrBlobPtr类就仍能使用指向vector的weak_ptr了。你修改后的类将需要一个拷贝构造函数和一个拷贝赋值运算符,但不需要析构函数。解释拷贝构造函数的拷贝赋值运算符必须要做什么。解释为什么不需要析构函数。
“拷贝构造函数和拷贝赋值函数的作用是:保证类的对象在拷贝时可以自动分配内存,而不是指向右值的内存。
不需要析构函数的原因:StrBlob类中使用的是shared_ptr,可以自动管理内存,在离开作用域时自动销毁。”
练习题13.26
对上一题描述中的StrBlob类,编写你自己的版本。
在StrBlob类中添加智能指针:
StrBlob (const StrBlob& sb)
{
data = make_shared<vector<string>>(*sb.data);
}
StrBlob& operator= (const StrBlob& sb)
{
data = make_shared<std::vector<string>>(*sb.data);
return *this;
}
练习题13.27
定义你自己的使用引用计数版本的HasPtr。
#include <string>
class HasPtr {
public:
HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0), use(new size_t(1)) { }
HasPtr(const HasPtr &hp) : ps(hp.ps), i(hp.i), use(hp.use) { ++*use; }
HasPtr& operator=(const HasPtr &rhs) {
++*rhs.use;
if (--*use == 0) {
delete ps;
delete use;
}
ps = rhs.ps;
i = rhs.i;
use = rhs.use;
return *this;
}
~HasPtr() {
if (--*use == 0) {
delete ps;
delete use;
}
}
private:
std::string *ps;
int i;
size_t *use;
};
练习题13.28
给定下面的类,为其实现一个默认构造函数和必要的拷贝控制成员。
// .h文件
class TreeNode
{
public:
TreeNode () : value(std::string()), count (new int(1)), left(nullptr), right(nullptr) {}
TreeNode (const TreeNode& tn) : value (tn.value), count (tn.count), left (tn.left), right(tn.right) { ++ *count; }
TreeNode& operator= (const TreeNode& tn);
~ TreeNode()
{
if (0 == -- *count) {
delete left;
delete right;
delete count;
}
}
private:
std::string value;
int
*count;
TreeNode
*left;
TreeNode
*right;
};
class BinStrTree
{
public:
BinStrTree() : root (new TreeNode()) {}
BinStrTree(const BinStrTree& bst) : root (bst.root) {}
BinStrTree& operator= (const BinStrTree& bst);
~ BinStrTree () { delete root; }
private:
TreeNode *root;
};
// .cpp 文件
TreeNode& TreeNode::operator= (const TreeNode& tn)
{
++ *tn.count;
if (0 == *--count) {
delete left;
delete right;
delete count;
}
value = tn.value;
count = tn.count;
left = tn.left;
right = tn.right;
}
BinStrTree& BinStrTree::operator= (const BinStrTree& bst)
{
TreeNode *new_root = new TreeNode(*bst.root);
delete root;
root = new_root;
return *this;
}
练习题13.29
解释swap(HasPtr&, HasPtr&)中对swap的调用不会导致递归循环。
在void swap(HasPtr&, HasPtr&)
函数中,使用了using std::swap;
,因此swap (lhs.ps, rhs.ps);
其实是调用了std::swap(string*, string*)
。同理,swap (lhs.i, rhs.i);
调用的是std::swap(int, int)
。调用了不同的swap函数,因此不会导致递归循环。
练习题13.30
为你的类值版本的HasPtr编写swap函数,并测试它。为你的swap函数添加一个打印语句,指出函数什么时候执行。
//.h文件
#include <string>
#include <iostream>
class HasPtr {
public:
friend void swap(HasPtr&, HasPtr&);
HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }
HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }
HasPtr& operator=(const HasPtr &hp) {
auto new_p = new std::string(*hp.ps);
delete ps;
ps = new_p;
i = hp.i;
return *this;
}
~HasPtr() {
delete ps;
}
void show() { std::cout << *ps << std::endl; }
private:
std::string *ps;
int i;
};
inline
void swap(HasPtr& lhs, HasPtr& rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
std::cout << "调用 swap(HasPtr& lhs, HasPtr& rhs)" << std::endl;
}
//.cpp文件
int main()
{
HasPtr hp1 ("test");
HasPtr hp2 ("train");
swap(hp1, hp2);
hp1.show();
hp2.show();
return 0;
}
测试:
调用 swap(HasPtr& lhs, HasPtr& rhs)
train
test
练习题13.31
为你的HasPtr类定义一个<运算符,并定义一个HasPtr的vector。为这个vector添加一些元素,并对它执行sort。注意何时会调用swap。
//.h文件
#include <string>
#include <iostream>
class HasPtr
{
public:
friend void swap(HasPtr&, HasPtr&);
friend bool operator<(const HasPtr &lhs, const HasPtr &rhs);
HasPtr(const std::string &s = std::string())
: ps(new std::string(s)), i(0)
{ }
HasPtr(const HasPtr &hp)
: ps(new std::string(*hp.ps)), i(hp.i)
{ }
HasPtr& operator=(HasPtr tmp)
{
this->swap(tmp);
return *this;
}
~HasPtr()
{
delete ps;
}
void swap(HasPtr &rhs)
{
using std::swap;
swap(ps, rhs.ps);
swap(i, rhs.i);
std::cout << "调用 swap(HasPtr &rhs)" << std::endl;
}
void show() const
{
std::cout << *ps << std::endl;
}
private:
std::string *ps;
int i;
};
void swap(HasPtr& lhs, HasPtr& rhs)
{
lhs.swap(rhs);
}
bool operator<(const HasPtr &lhs, const HasPtr &rhs)
{
return *lhs.ps < *rhs.ps;
}
//.cpp文件
#include <vector>
#include <algorithm>
int main(void)
{
std::cout << "定义HasPtr" << std::endl;
HasPtr s{ "train" }, a{ "test" }, c{ "fit" };
std::cout << "定义 vector<HasPtr>" << std::endl;
std::vector<HasPtr> vec{ s, a, c };
std::cout << "使用sort排序" << std::endl;
std::sort(vec.begin(), vec.end());
for (auto const& elem : vec) elem.show();
return 0;
}
测试:
定义HasPtr
定义 vector<HasPtr>
使用sort排序
调用 swap(HasPtr &rhs)
调用 swap(HasPtr &rhs)
调用 swap(HasPtr &rhs)
调用 swap(HasPtr &rhs)
调用 swap(HasPtr &rhs)
fit
test
train
练习题13.32
类指针的HasPtr版本会从swap函数受益吗?如果会,得到了什么益处?如果不是,为什么?
类指针的版本,不需要中间变量,即不用创建新的副本,直接使用std::swap函数就可以实现指针交换,节省内存空间。
练习题13.33
为什么Message的成员save和remove的参数是一个Folder&?为什么我们不将参数定义为Folder或是const Folder&?
“不定义为Folder的原因是:如果是值传递,则会在传参的时候传递参数的副本,执行修改参数的操作,修改的是参数的副本。而且要为副本重新分配内存,浪费空间。
不定义为const Folder&的原因是:在save和remove中都要改变参数,因此不能定义为const类型。”
练习题13.34
编写本节所描述的Message。
// .h 文件
#include <string>
#include <set>
class Folder;
class Message
{
friend class Folder;
friend void swap (Message& lhs, Message& rhs);
public:
explicit Message (const std::string& str = "") : contents(str) {}
Message (const Message&);
Message& operator= (const Message&);
~ Message ();
void save (Folder &f);
void remove (Folder &f);
private:
std::string contents;
// 实际消息文本
std::set<Folder*> folders;
// 包含本Message的Folder
void add_to_Folders (const Message&);
void remove_from_Folders ();
};
void swap(Message& lhs, Message& rhs)
{
using std::swap;
for (auto f : lhs.folders) {
f->remMsg(&lhs);
}
for (auto f : rhs.folders) {
f->remMsg(&rhs);
}
swap (lhs.folders, rhs.folders);
swap (lhs.contents, rhs.contents);
for (auto f : lhs.folders) {
f->addMsg (&lhs);
}
for (auto f : rhs.folders) {
f->addMsg(&rhs);
}
}
// .cpp文件
void Message::save (Folder &f)
{
folders.insert (&f);
f.addMsg(this);
}
void Message::remove (Folder &f)
{
folders.erase(&f);
f.remMsg(this);
}
void Message::add_to_Folders (const Message& m)
{
for (auto f : m.folders) {
f->addMsg(this);
}
}
Message::Message (const Message& m) : contents (m.contents), folders(m.folders)
{
add_to_Folders (m);
}
void Message::remove_from_Folders ()
{
for (auto f : folders) {
f->remMsg(this);
}
}
Message::~Message()
{
remove_from_Folders();
}
Message& Message::operator= (const Message& rhs)
{
this->remove_from_Folders();
// 先从当前文件中移除
this->contents = rhs.contents;
this->folders = rhs.folders;
add_to_Folders(rhs);
return *this;
}
练习题13.35
如果Message使用合成的拷贝控制成员,将会发生什么?
“如果使用合成的拷贝控制成员,则当拷贝一个Message对象时,不会向folder中添加新的Message,folder将不会同步更新。”
练习题13.36
设计并实现对应的Folder类。此类应该保存一个指向Folder中包含的Message的set。
class Folder {
friend void swap(Folder &, Folder &);
friend class Message;
public:
Folder() = default;
Folder(const Folder &);
Folder& operator=(const Folder &);
~Folder();
void print_debug();
private:
std::set<Message*> msgs;
void add_to_Message(const Folder&);
void remove_from_Message();
void addMsg(Message *m) { msgs.insert(m); }
void remMsg(Message *m) { msgs.erase(m); }
};
void swap(Folder &, Folder &);
练习题13.37
为Message类添加成员,实现folders添加或删除一个给定的Folder*。这两个成员类似Folder类的addMsg和remMsg操作。
// 在Message类中加:
void addFolder (Folder* f)
{
folders.insert(f);
}
void deleteFolder (Folder* f)
{
folders.erase(f);
}
练习题13.38
我们并未使用拷贝和交换方式来设计Message的赋值运算符。你认为其原因是什么?
“当涉及到动态分配时,使用拷贝和交换方式来实现赋值运算符是一个很好的方式(因为又共同的delete操作)。但Message类并未涉及到动态分配,此时如果使用拷贝和交换的方式就没有意义。”
练习题13.39
编写你自己版本的StrVec,包括自己版本的reserve、capacity和resize。
// .h 文件
#include <memory>
#include <string>
class StrVec
{
public:
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
StrVec(const StrVec&);
StrVec& operator=(const StrVec&);
~StrVec();
void push_back(const std::string&);
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
std::string *begin() const { return elements; }
std::string *end() const { return first_free; }
void reserve(size_t new_cap);
void resize(size_t count);
void resize(size_t count, const std::string&);
private:
std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
void free();
void chk_n_alloc() { if (size() == capacity()) reallocate(); }
void reallocate();
void alloc_n_move(size_t new_cap);
private:
std::string *elements;
std::string *first_free;
std::string *cap;
std::allocator<std::string> alloc;
};
// .cpp文件
void StrVec::push_back(const std::string &s)
{
chk_n_alloc();
alloc.construct(first_free++, s);
}
std::pair<std::string*, std::string*>
StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
auto data = alloc.allocate(e - b);
return{ data, std::uninitialized_copy(b, e, data) };
}
void StrVec::free()
{
if (elements) {
for (auto p = first_free; p != elements;)
alloc.destroy(--p);
alloc.deallocate(elements, cap - elements);
}
}
StrVec::StrVec(const StrVec &rhs)
{
auto newdata = alloc_n_copy(rhs.begin(), rhs.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec::~StrVec()
{
free();
}
StrVec& StrVec::operator = (const StrVec &rhs)
{
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
void StrVec::alloc_n_move(size_t new_cap)
{
auto newdata = alloc.allocate(new_cap);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + new_cap;
}
void StrVec::reallocate()
{
auto newcapacity = size() ? 2 * size() : 1;
alloc_n_move(newcapacity);
}
void StrVec::reserve(size_t new_cap)
{
if (new_cap <= capacity()) return;
alloc_n_move(new_cap);
}
void StrVec::resize(size_t count)
{
resize(count, std::string());
}
void StrVec::resize(size_t count, const std::string &s)
{
if (count > size()) {
if (count > capacity()) reserve(count * 2);
for (size_t i = size(); i != count; ++i)
alloc.construct(first_free++, s);
}
else if (count < size()) {
while (first_free != elements + count)
alloc.destroy(--first_free);
}
}
int main()
{
return 0;
}
练习题13.40
为你的StrVec类添加一个构造函数,它接受一个initializer_list参数。
// .h文件中添加:
public:
StrVec(std::initializer_list<std::string>& il);
private:
void range_initial(const std::string* first, const std::string* last);
// .cpp文件中添加:
void StrVec::range_initial(const string* first, const string* last)
{
auto newdata = alloc_n_copy (first, last);
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec::StrVec(std::initializer_list<string>& il)
{
range_initial (il.begin(), il.end());
}
练习题13.41
在push_back中,我们为什么在construct调用中使用前置递增运算?如果使用后置递增运算的话,会发生什么?。(P466,construct使用的是后置,题目有误)
前置运算,是先加后使用。后置运算,是先使用再加,如果使用前置运算,则first_free先向后移动,再插入,此时first_free指向第二个空位置,则中间有空余的位置。
练习题13.42
在你的TextQuery和QueryResult类中用你的StrVec类代替vector,以此来测试你的StrVec类。
附上GitHub上的代码:
ex13_42_TextQuery.h
#ifndef CP5_TEXTQUERY_H_
#define CP5_TEXTQUERY_H_
#include <string>
#include <memory>
#include <iostream>
#include <fstream>
#include <map>
#include <set>
#include "ex13_42_StrVec.h"
class QueryResult;
class TextQuery {
public:
TextQuery(std::ifstream &);
QueryResult query(const std::string&) const;
private:
std::shared_ptr<StrVec> input;
std::map<std::string, std::shared_ptr<std::set<size_t>>> result;
};
class QueryResult {
public:
friend std::ostream& print(std::ostream &, const QueryResult&);
public:
QueryResult(const std::string &s, std::shared_ptr<std::set<size_t>> set, std::shared_ptr<StrVec> v) : word(s), nos(set), input(v) { }
private:
std::string word;
std::shared_ptr<std::set<size_t>> nos;
std::shared_ptr<StrVec> input;
};
std::ostream& print(std::ostream &, const QueryResult&);
#endif
ex13_42_TextQuery.cpp
#include "ex13_42_TextQuery.h"
#include <sstream>
#include <algorithm>
using std::string;
TextQuery::TextQuery(std::ifstream &ifs) : input(new StrVec)
{
size_t lineNo = 0;
for (string line; std::getline(ifs, line); ++lineNo) {
input->push_back(line);
std::istringstream line_stream(line);
for (string text, word; line_stream >> text; word.clear()) {
// avoid read a word followed by punctuation(such as: word, )
std::remove_copy_if(text.begin(), text.end(), std::back_inserter(word), ispunct);
// use reference avoid count of shared_ptr add.
auto &nos = result[word];
if (!nos) nos.reset(new std::set<size_t>);
nos->insert(lineNo);
}
}
}
QueryResult TextQuery::query(const string& str) const
{
// use static just allocate once.
static std::shared_ptr<std::set<size_t>> nodate(new std::set<size_t>);
auto found = result.find(str);
if (found == result.end()) return QueryResult(str, nodate, input);
else return QueryResult(str, found->second, input);
}
std::ostream& print(std::ostream &out, const QueryResult& qr)
{
out << qr.word << " occurs " << qr.nos->size() << (qr.nos->size() > 1 ? " times" : " time") << std::endl;
for (auto i : *qr.nos)
out << "t(line " << i+1 << ") " << qr.input->at(i) << std::endl;
return out;
}
ex13_42_StrVec.h
#ifndef CP5_STRVEC_H_
#define CP5_STRVEC_H_
#include <memory>
#include <string>
#include <initializer_list>
class StrVec
{
public:
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
StrVec(const StrVec&);
StrVec(std::initializer_list<std::string>);
StrVec& operator=(const StrVec&);
~StrVec();
void push_back(const std::string&);
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
std::string *begin() const { return elements; }
std::string *end() const { return first_free; }
std::string& at(size_t pos) { return *(elements + pos); }
const std::string& at(size_t pos) const { return *(elements + pos); }
void reserve(size_t new_cap);
void resize(size_t count);
void resize(size_t count, const std::string&);
private:
std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
void free();
void chk_n_alloc() { if (size() == capacity()) reallocate(); }
void reallocate();
void alloc_n_move(size_t new_cap);
void range_initialize(const std::string*, const std::string*);
private:
std::string *elements;
std::string *first_free;
std::string *cap;
std::allocator<std::string> alloc;
};
#endif
ex13_42_StrVec.cpp
#include "ex13_42_StrVec.h"
std::allocator<std::string> StrVec::alloc;
void StrVec::push_back(const std::string &s)
{
chk_n_alloc();
alloc.construct(first_free++, s);
}
std::pair<std::string*, std::string*>
StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
auto data = alloc.allocate(e - b);
return{ data, std::uninitialized_copy(b, e, data) };
}
void StrVec::free()
{
if (elements) {
for (auto p = first_free; p != elements;)
alloc.destroy(--p);
alloc.deallocate(elements, cap - elements);
}
}
void StrVec::range_initialize(const std::string *first, const std::string *last)
{
auto newdata = alloc_n_copy(first, last);
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec::StrVec(const StrVec &rhs)
{
range_initialize(rhs.begin(), rhs.end());
}
StrVec::StrVec(std::initializer_list<std::string> il)
{
range_initialize(il.begin(), il.end());
}
StrVec::~StrVec()
{
free();
}
StrVec& StrVec::operator = (const StrVec &rhs)
{
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
void StrVec::alloc_n_move(size_t new_cap)
{
auto newdata = alloc.allocate(new_cap);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + new_cap;
}
void StrVec::reallocate()
{
auto newcapacity = size() ? 2 * size() : 1;
alloc_n_move(newcapacity);
}
void StrVec::reserve(size_t new_cap)
{
if (new_cap <= capacity()) return;
alloc_n_move(new_cap);
}
void StrVec::resize(size_t count)
{
resize(count, std::string());
}
void StrVec::resize(size_t count, const std::string &s)
{
if (count > size()) {
if (count > capacity()) reserve(count * 2);
for (size_t i = size(); i != count; ++i)
alloc.construct(first_free++, s);
}
else if (count < size()) {
while (first_free != elements + count)
alloc.destroy(--first_free);
}
}
ex13_42.cpp
#include "ex13_42_TextQuery.h"
#include <iostream>
void runQueries(std::ifstream &infile)
{
TextQuery tq(infile);
while (true) {
std::cout << "enter word to look for, or q to quit: ";
std::string s;
if (!(std::cin >> s) || s == "q") break;
print(std::cout, tq.query(s)) << std::endl;
}
}
int main()
{
std::ifstream file("../data/storyDataFile.txt");
runQueries(file);
}
练习题13.43
重写free成员,用for_each和lambda来代替for循环destroy元素。你更倾向于哪种实现,为什么?
for_each和lambda一起使用更简洁,但是for循环更符合阅读习惯:for_each(elements, first_free, [this](string& p){ alloc.destroy(&p); });
练习题13.44
编写标准库string类的简化版本,命名为String。你的类应该至少有一个默认构造函数和一个接受C风格字符串指针参数的构造函数。使用allocator为你的String类分配所需内存
// .h文件
#include <memory>
class String
{
public:
String() : String("") {}
String (const char* s);
String (const String& s);
String& operator= (String& s);
~ String();
void free();
const char* c_str() { return begin; }
size_t size() { return end - begin; }
size_t length() { return end - begin - 1; }
private:
std::pair<char*, char*> alloc_n_copy (const char* beg, const char* end);
void range_initial(const char* first, const char* last);
private:
char *begin;
char *end;
std::allocator<char> alloc;
};
// .cpp 文件
#include "String.h"
#include <string.h>
using namespace std;
pair<char*, char*> String::alloc_n_copy (const char* beg, const char* end)
{
auto str = alloc.allocate (end - beg);
return {str, uninitialized_copy(beg, end, str)};
}
void String::range_initial (const char* first, const char* last)
{
auto newString = alloc_n_copy (first, last);
begin = newString.first;
end = newString.second;
}
String::String(const char* s)
{
range_initial (s, s+sizeof(s));
}
String::String (const String& s)
{
range_initial (s.begin, s.end);
cout << "拷贝构造函数" << endl;
}
void String::free()
{
if (begin) {
for (auto i = begin; i != end; ++ i) {
alloc.destroy (i);
}
alloc.deallocate (begin, end - begin);
}
}
String& String::operator= (String& s)
{
auto new_s = alloc_n_copy (s.begin, s.end);
free();
begin = new_s.first;
end = new_s.second;
cout << "拷贝赋值运算符" << endl;
return *this;
}
练习题13.45
解释右值引用和左值引用的区别。
P471。
左值引用,也就是常规引用,返回左值的函数有赋值、下标、解引用和前置递增/递减运算符,左值有持久的状态。
右值引用就是必须绑定到右值上的引用,返回右值的函数包括算术、关系、位及后置递增/递减运算符,右值要么是字面常量,要么就是在表达式求值过程中创建的临时对象。
练习题13.46
什么类型的引用可以绑定到下面的初始化器上?
int f();
vector<int> vi(100);
int? r1 = f();
// f()的返回值相当于一个常量,只能做右值引用或const引用,所以是int &&r = f(); 或者const int &r1 = f();
int? r2 = vi[0];
// 下标运算返回左值,所以是int &r = vi[0]
int? r3 = r1;
// r1此时是等价于变量,所以是int &r3 = r1;
int? r4 = vi[0] * f();
// 算术运算产生右值,所以是int &&r4 = vi[0] * f();
练习题13.47
对你在练习13.44中定义的String类,为它的拷贝构造函数和拷贝赋值运算符添加一条语句,在每次函数执行时打印一条信息。
同13.44。
练习题13.48
定义一个vector并在其上多次调用push_back。运行你的程序,并观察String被拷贝了多少次。
// .h 文件
#include <memory>
class String
{
public:
String() : String("") { }
String(const char *);
String(const String&);
String& operator=(const String&);
~String();
const char *c_str() const { return elements; }
size_t size() const { return end - elements; }
size_t length() const { return end - elements - 1; }
private:
std::pair<char*, char*> alloc_n_copy(const char*, const char*);
void range_initializer(const char*, const char*);
void free();
private:
char *elements;
char *end;
std::allocator<char> alloc;
};
// .cpp文件
#include <algorithm>
#include <iostream>
std::pair<char*, char*>
String::alloc_n_copy(const char *b, const char *e)
{
auto str = alloc.allocate(e - b);
return{ str, std::uninitialized_copy(b, e, str) };
}
void String::range_initializer(const char *first, const char *last)
{
auto newstr = alloc_n_copy(first, last);
elements = newstr.first;
end = newstr.second;
}
String::String(const char *s)
{
char *sl = const_cast<char*>(s);
while (*sl)
++sl;
range_initializer(s, ++sl);
std::cout << "使用range_initializer" << std::endl;
}
String::String(const String& rhs)
{
range_initializer(rhs.elements, rhs.end);
std::cout << "拷贝构造函数" << std::endl;
}
void String::free()
{
if (elements) {
std::for_each(elements, end, [this](char &c){ alloc.destroy(&c); });
alloc.deallocate(elements, end - elements);
}
}
String::~String()
{
free();
}
String& String::operator = (const String &rhs)
{
auto newstr = alloc_n_copy(rhs.elements, rhs.end);
free();
elements = newstr.first;
end = newstr.second;
std::cout << "拷贝赋值运算符" << std::endl;
return *this;
}
// .cpp测试文件
#include <vector>
#include <iostream>
void foo(String x)//String x 拷贝构造
{
std::cout << x.c_str() << std::endl;
}
void bar(const String& x)
{
std::cout << x.c_str() << std::endl;
}
String baz()
{
String ret("overfit");
return ret;
}
int main()
{
char text[] = "test";
std::cout << "--------定义---------" << std::endl;
String s0;
String s1("train");
String s2(s0);//拷贝构造
String s3 = s1;//拷贝构造
String s4(text);
s2 = s1;//拷贝赋值
std::cout << "--------调用---------" << std::endl;
foo(s1);//String x 拷贝构造
bar(s1);
foo("CNN");//使用range_initializer
bar("CNN");//使用range_initializer
String s5 = baz();//使用range_initializer
std::cout << "--------添加---------" << std::endl;
std::vector<String> svec;
svec.reserve(8);
svec.push_back(s0);
svec.push_back(s1);
svec.push_back(s2);
svec.push_back(s3);
svec.push_back(s4);
svec.push_back(s5);
svec.push_back(baz());//使用range_initializer + 拷贝构造
svec.push_back("good fit");//使用range_initializer + 拷贝构造
std::cout << "--------输出---------" << std::endl;
for (const auto &s : svec) {
std::cout << s.c_str() << std::endl;
}
return 0;
}
测试:
--------定义---------
使用range_initializer
使用range_initializer
拷贝构造函数
拷贝构造函数
使用range_initializer
拷贝赋值运算符
--------调用---------
拷贝构造函数
train
train
使用range_initializer
CNN
使用range_initializer
CNN
使用range_initializer
--------添加---------
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
使用range_initializer
拷贝构造函数
使用range_initializer
拷贝构造函数
--------输出---------
train
train
train
test
overfit
overfit
good fit
练习题13.49
为你的StrVec、String和Message类添加一个移动构造函数和一个移动赋值运算符。
搬运参考博主的了
// Str.h文件中加:
StrVec (StrVec&& rhs) noexcept;
StrVec& operator= (StrVec&& rhs) noexcept;
// StrVec.cpp中加:
StrVec::StrVec(StrVec&& rhs) noexcept : elements (rhs.elements), first_free (rhs.first_free), cap (rhs.cap)
{
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
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;
}
//String.h
String (String&& s) noexcept;
String& operator= (String&& s) noexcept;
// String.cpp中加:
String::String(String&& s) noexcept : begin (s.begin), end (s.end)
{
s.begin = s.end = nullptr;
}
String& String::operator= (String&& s) noexcept
{
if (this != &s) {
free();
begin = s.begin;
end = s.end;
s.begin = s.end = nullptr;
}
return *this;
}
// Message.h
Message (Message&& m);
Message& operator= (Message&& m);
void moveFolders (Message* m);
// Message.cpp
void Message::moveFolders (Message* m)
{
folders = std::move(m->folders);
for (auto f : folders) {
f->remMsg(m);
f->addMsg(this);
}
m->folders.clear();
}
Message::Message (Message&& m) : contents (std::move(m.contents))
{
moveFolders(&m);
}
Message& Message::operator=(Message&& m)
{
if (this != &m) {
remove_from_Folders();
contents (std::move(m.contents));
moveFolders(&m);
}
return *this;
}
练习题13.50
在你的String类的移动操作中添加打印语句,并重新运行13.6.1节的练习13.48中的程序,它使用了一个vector,观察什么时候会避免拷贝。
//ex13_49_String.h
#include <memory>
#ifndef _MSC_VER
#define NOEXCEPT noexcept
#else
#define NOEXCEPT
#endif
class String
{
public:
String() : String("") { }
String(const char *);
String(const String&);
String& operator=(const String&);
String(String &&) NOEXCEPT;
String& operator=(String&&)NOEXCEPT;
~String();
const char *c_str() const { return elements; }
size_t size() const { return end - elements; }
size_t length() const { return end - elements - 1; }
private:
std::pair<char*, char*> alloc_n_copy(const char*, const char*);
void range_initializer(const char*, const char*);
void free();
private:
char *elements;
char *end;
std::allocator<char> alloc;
};
//ex13_49_String.cpp
#include <algorithm>
#include <iostream>
std::pair<char*, char*>
String::alloc_n_copy(const char *b, const char *e)
{
auto str = alloc.allocate(e - b);
return{ str, std::uninitialized_copy(b, e, str) };
}
void String::range_initializer(const char *first, const char *last)
{
auto newstr = alloc_n_copy(first, last);
elements = newstr.first;
end = newstr.second;
}
String::String(const char *s)
{
char *sl = const_cast<char*>(s);
while (*sl)
++sl;
range_initializer(s, ++sl);
std::cout << "拷贝构造函数String(const char *s)" << std::endl;
}
String::String(const String& rhs)
{
range_initializer(rhs.elements, rhs.end);
std::cout << "拷贝构造函数String(const String& rhs)" << std::endl;
}
void String::free()
{
if (elements) {
std::for_each(elements, end, [this](char &c){ alloc.destroy(&c); });
alloc.deallocate(elements, end - elements);
}
}
String::~String()
{
free();
}
String& String::operator = (const String &rhs)
{
auto newstr = alloc_n_copy(rhs.elements, rhs.end);
free();
elements = newstr.first;
end = newstr.second;
return *this;
}
String::String(String &&s) NOEXCEPT : elements(s.elements), end(s.end)
{
s.elements = s.end = nullptr;
std::cout << "运行String(String &&s) NOEXCEPT" << std::endl;
}
String& String::operator = (String &&rhs) NOEXCEPT
{
if (this != &rhs) {
free();
elements = rhs.elements;
end = rhs.end;
rhs.elements = rhs.end = nullptr;
}
std::cout << "String::operator = (String &&rhs) NOEXCEPT" << std::endl;
return *this;
}
// .cpp测试文件
#include <vector>
#include <iostream>
void foo(String x)//String x 拷贝构造
{
std::cout << x.c_str() << std::endl;
}
void bar(const String& x)
{
std::cout << x.c_str() << std::endl;
}
String baz()
{
String ret("overfit");
return ret;
}
int main()
{
char text[] = "test";
std::cout << "--------定义---------" << std::endl;
String s0;
String s1("train");
String s2(s0);//拷贝构造
String s3 = s1;//拷贝构造
String s4(text);
s2 = s1;//拷贝赋值
std::cout << "--------调用---------" << std::endl;
foo(s1);//String x 拷贝构造
bar(s1);
foo("CNN");//使用range_initializer
bar("CNN");//使用range_initializer
String s5 = baz();//使用range_initializer
std::cout << "--------添加---------" << std::endl;
std::vector<String> svec;
svec.reserve(8);
svec.push_back(s0);
svec.push_back(s1);
svec.push_back(s2);
svec.push_back(s3);
svec.push_back(s4);
svec.push_back(s5);
svec.push_back(baz());//使用range_initializer + 拷贝构造
svec.push_back("good fit");//使用range_initializer + 拷贝构造
std::cout << "--------输出---------" << std::endl;
for (const auto &s : svec) {
std::cout << s.c_str() << std::endl;
}
return 0;
}
测试:
--------定义---------
拷贝构造函数String(const char *s)
拷贝构造函数String(const char *s)
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const char *s)
--------调用---------
拷贝构造函数String(const String& rhs)
train
train
拷贝构造函数String(const char *s)
CNN
拷贝构造函数String(const char *s)
CNN
拷贝构造函数String(const char *s)
--------添加---------
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const String& rhs)
拷贝构造函数String(const char *s)
运行String(String &&s) NOEXCEPT
拷贝构造函数String(const char *s)
运行String(String &&s) NOEXCEPT
--------输出---------
train
train
train
test
overfit
overfit
good fit
练习题13.51
虽然unique_ptr不能拷贝,但我们在12.1.5节中编写了一个clone函数,它以值方式返回一个unique_ptr。解释为什么函数是合法的,以及为什么它能正确工作。
“不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr,编译器知道要返回的对象将要被销毁,因此会执行一种特殊的"拷贝" – 移动。最常见的就是从函数返回unique_ptr。”
练习题13.52
详细解释第478页中的HasPtr对象的赋值发生了什么?特别是,一步一步描述hp、hp2以及HasPtr的赋值运算符中的参数rhs的值发生了什么变化
P478
hp = hp2;
hp2是一个左值,hp2通过拷贝构造函数来拷贝,拷贝构造函数将分配一个新的string,并拷贝hp2指向的string。
hp = std::move(hp2)
移动构造函数移动hp2,调用std::move将一个右值引用绑定到hp2上。在此情况下,拷贝构造函数和移动构造函数都是可行的。由于实参是一个右值引用,所以移动构造函数是精确匹配的。因此移动构造函数从hp2拷贝指针,而不会分配内存。
练习题13.53
从底层效率的角度看,HasPtr的赋值运算符并不理想,解释为什么。为HasPtr实现一个拷贝赋值运算符和一个移动赋值运算符,并比较你的新的移动赋值运算符中执行的操作与拷贝并交换版本中执行的操作。
这是因为调用的swap操作每次交换时,都会创建临时空间,用来存放临时变量的值,所以赋值运算效率低。
//.h
#include <string>
class HasPtr {
public:
friend void swap(HasPtr&, HasPtr&);
HasPtr(const std::string &s = std::string());
HasPtr(const HasPtr &hp);
HasPtr(HasPtr &&p) noexcept;
HasPtr& operator=(HasPtr rhs);
//HasPtr& operator=(const HasPtr &rhs);
//HasPtr& operator=(HasPtr &&rhs) noexcept;
~HasPtr();
private:
std::string *ps;
int i;
};
//.cpp
#include <iostream>
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
std::cout << "调用交换swap函数" << std::endl;
}
HasPtr::HasPtr(const std::string &s) : ps(new std::string(s)), i(0)
{
std::cout << "调用构造函数" << std::endl;
}
HasPtr::HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i)
{
std::cout << "调用拷贝构造函数" << std::endl;
}
HasPtr::HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i)
{
p.ps = 0;
std::cout << "调用构造函数" << std::endl;
}
HasPtr& HasPtr::operator=(HasPtr rhs)//HasPtr rhs会调用拷贝构造函数
{
std::cout << "拷贝赋值运算符" << std::endl;
swap(*this, rhs);
return *this;
}
HasPtr::~HasPtr()
{
std::cout << "调用析构函数" << std::endl;
delete ps;
}
int main()
{
std::cout << "------测试一-------" << std::endl;
HasPtr hp1("test"), hp2("train"), *pH = new HasPtr("train");
std::cout << "------测试二-------" << std::endl;
hp1 = hp2;
std::cout << "------测试三-------" << std::endl;
hp1 = std::move(*pH);
}
测试:
------测试一-------
调用构造函数
调用构造函数
调用构造函数
------测试二-------
调用拷贝构造函数
拷贝赋值运算符
调用交换swap函数
调用析构函数
------测试三-------
调用构造函数
拷贝赋值运算符
调用交换swap函数
调用析构函数
调用析构函数
调用析构函数
练习题13.54
如果我们为HasPtr定义了移动赋值运算符,但未改变拷贝并交换运算符,会发生什么?编写代码验证你的答案。
“会报错:
error: ambiguous overload for ‘operator=’ (operand types are ‘HasPtr’ and ‘std::remove_reference<HasPtr&>::type {aka HasPtr}’)|”
练习题13.55
为你的StrBlob添加一个右值引用版本的push_back。
为:void push_back (string&& t) { data->push_back(std::move(t)); }
练习题13.56
如果sorted定义如下,会发生什么:
Foo Foo::sorted() const& {
Foo ret(*this);
return ret.sorted();
}
ret是一个左值,因此调用ret.sorted()时不会匹配到Foo sorted() &&右值版本,反而继续调用自身Foo sorted() const&,导致程序无限递归,不断调用本身,并且不停复制this,造成堆栈溢出。
练习题13.57
如果sorted定义如下,会发生什么:
Foo Foo::sorted() const & { return Foo(*this).sorted(); }
与13.56不同,Foo(*this)返回的是一个右值,能够调用sorted的右值版本,返回排序后的Foo。
练习题13.58
编写新版本的Foo类,其sorted函数中有打印语句,测试这个类,来验证你对前两题的答案是否正确。
#include <vector>
#include <iostream>
#include <algorithm>
using std::vector; using std::sort;
class Foo {
public:
Foo sorted() &&;
Foo sorted() const &;
private:
vector<int> data;
};
Foo Foo::sorted() && {
sort(data.begin(), data.end());
std::cout << "&&" << std::endl; // 调试信息
return *this;
}
Foo Foo::sorted() const & {
std::cout << "const &" << std::endl; // 调试信息
//
Foo ret(*this);
//
ret.sorted();
// 练习题 13.56
//
return ret;
return Foo(*this).sorted();//右值引用 // 练习题 13.57
}
int main()
{
Foo().sorted(); // 调用 "&&"
Foo f;
f.sorted(); // 调用 "const &"
}
测试:
&&
const &
&&
最后
以上就是迷路自行车为你收集整理的C++ Primer(第五版)|练习题答案与解析(第十三章:拷贝控制)C++ Primer(第五版)|练习题答案与解析(第十三章:拷贝控制)的全部内容,希望文章能够帮你解决C++ Primer(第五版)|练习题答案与解析(第十三章:拷贝控制)C++ Primer(第五版)|练习题答案与解析(第十三章:拷贝控制)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复