# Chapter 12 Dynamic Memory

  • 对象的生命周期:

    • 全局对象在程序启动时分配,结束时销毁。
    • 局部对象在进入程序块时创建,离开块时销毁。
    • 局部 static 对象在第一次使用前分配,在程序结束时销毁。
    • 动态分配对象:只能显式地被释放。
  • 对象的内存位置:

    • 静态内存用来保存局部 static 对象、类 static 对象、定义在任何函数之外的变量。
    • 栈内存用来保存定义在函数内的非 static 对象。
    • 堆内存,又称自由空间,用来存储动态分配的对象。

# Dynamic Memory and Smart Pointers


# 动态内存与智能指针

  • 动态内存管理:
    • new :在动态内存中为对象分配空间并返回一个指向该对象的指针。
    • delete :接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
  • 智能指针:
    • 管理动态对象。
    • 行为类似常规指针。
    • 负责自动释放所指向的对象。
    • 智能指针也是模板。

# shared_ptr 类

shared_ptr 和 unique_ptr 都支持的操作

操作解释
shared_ptr<T> sp unique_ptr<T> up空智能指针,可以指向类型是 T 的对象
pp 用作一个条件判断,若 p 指向一个对象,则为 true
*p解引用 p ,获得它指向的对象。
p->mem等价于 (*p).mem
p.get()返回 p 中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。
swap(p, q) p.swap(q)交换 pq 中的指针

shared_ptr 独有的操作

操作解释
make_shared<T>(args)返回一个 shared_ptr ,指向一个动态分配的类型为 T 的对象。使用 args 初始化此对象。
shared_ptr<T>p(q)pshared_ptr q 的拷贝;此操作会递增 q 中的计数器。 q 中的指针必须能转换为 T*
p = qpq 都是 shared_ptr ,所保存的指针必须能互相转换。此操作会递减 p 的引用计数,递增 q 的引用计数;若 p 的引用计数变为 0,则将其管理的原内存释放。
p.unique()p.use_count() 是 1,返回 true ;否则返回 false
p.use_count()返回与 p 共享对象的智能指针数量;可能很慢,主要用于调试。
  • 使用动态内存的三种原因
    • 程序不知道自己需要使用多少对象(比如容器类)。
    • 程序不知道所需要对象的准确类型。
    • 程序需要在多个对象间共享数据。

# 直接管理内存

  • new 动态分配和初始化对象。
    • new 无法为分配的对象命名(因为自由空间分配的内存是无名的),因此是返回一个指向该对象的指针。
    • int *pi = new int(123);
    • 一旦内存耗尽,会抛出类型是 bad_alloc 的异常。
  • delete 将动态内存归还给系统。
    • 接受一个指针,指向要释放的对象。
    • delete 后的指针称为空悬指针(dangling pointer)。
  • 使用 newdelete 管理动态内存存在三个常见问题:
    • 1. 忘记 delete 内存。
    • 2. 使用已经释放掉的对象。
    • 3. 同一块内存释放两次。
  • 坚持只使用智能指针可以避免上述所有问题。

# shared_ptr 和 new 结合使用

定义和改变 shared_ptr 的其他方法

操作解释
shared_ptr<T> p(q)p 管理内置指针 q 所指向的对象; q 必须指向 new 分配的内存,且能够转换为 T* 类型
shared_ptr<T> p(u)punique_ptr u 那里接管了对象的所有权;将 u 置为空
shared_ptr<T> p(q, d)p 接管了内置指针 q 所指向的对象的所有权。 q 必须能转换为 T* 类型。 p 将使用可调用对象 d 来代替 delete
shared_ptr<T> p(p2, d)pshared_ptr p2 的拷贝,唯一的区别是 p 将可调用对象 d 来代替 delete
p.reset()p 是唯一指向其对象的 shared_ptrreset 会释放此对象。若传递了可选的参数内置指针 q ,会令 p 指向 q ,否则会将 p 置空。若还传递了参数 d ,则会调用 d 而不是 delete 来释放 q
p.reset(q)同上
p.reset(q, d)同上

# 智能指针和异常

  • 如果使用智能指针,即使程序块由于异常过早结束,智能指针类也能确保在内存不需要的时候将其释放。
  • 智能指针陷阱
    • 不用相同的内置指针初始化(或 reset )多个智能指针
    • delete get() 返回的指针。
    • 如果你使用 get() 返回的指针,记得当最后一个对应的智能指针销毁后,你的指针就无效了。
    • 如果你使用智能指针管理的资源不是 new 分配的内存,记住传递给它一个删除器。

# unique_ptr

  • 某一个时刻只能有一个 unique_ptr 指向一个给定的对象。
  • 不支持拷贝或者赋值操作。
  • 向后兼容: auto_ptr :老版本,具有 unique_ptr 的部分特性。特别是,不能在容器中保存 auto_ptr ,也不能从函数返回 auto_ptr

unique_ptr 操作:

操作解释
unique_ptr<T> u1unique_ptr ,可以指向类型是 T 的对象。 u1 会使用 delete 来是释放它的指针。
unique_ptr<T, D> u2u2 会使用一个类型为 D 的可调用对象来释放它的指针。
unique_ptr<T, D> u(d)unique_ptr ,指向类型为 T 的对象,用类型为 D 的对象 d 代替 delete
u = nullptr释放 u 指向的对象,将 u 置为空。
u.release()u 放弃对指针的控制权,返回指针,并将 u 置空。
u.reset()释放 u 指向的对象
u.reset(q)u 指向 q 指向的对象
u.reset(nullptr)u 置空

# weak_ptr

  • weak_ptr 是一种不控制所指向对象生存期的智能指针。
  • 指向一个由 shared_ptr 管理的对象,不改变 shared_ptr 的引用计数。
  • 一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放,不管有没有 weak_ptr 指向该对象。

weak_ptr 操作:

操作解释
weak_ptr<T> wweak_ptr 可以指向类型为 T 的对象
weak_ptr<T> w(sp)shared_ptr 指向相同对象的 weak_ptrT 必须能转换为 sp 指向的类型。
w = pp 可以是 shared_ptr 或一个 weak_ptr 。赋值后 wp 共享对象。
w.reset()w 置为空。
w.use_count()w 共享对象的 shared_ptr 的数量。
w.expired()w.use_count() 为 0,返回 true ,否则返回 false
w.lock()如果 expiredtrue ,则返回一个空 shared_ptr ;否则返回一个指向 w 的对象的 shared_ptr

# Exercise 12.1

在此代码的结尾, b1b2 各包含多少个元素?

StrBlob b1;
{
	StrBlob b2 = {"a", "an", "the"};
	b1 = b2;
	b2.push_back("about");
}

解:

它们实际操作的是同一个 vector ,都包含 4 个元素。在代码的结尾, b2 被析构了,不影响 b1 的元素。

# Exercise 12.2

编写你自己的 StrBlob 类,包含 const 版本的 frontback

解:

头文件:

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <exception>
using std::vector; using std::string;
class StrBlob {
public:
    using size_type = vector<string>::size_type;
    StrBlob():data(std::make_shared<vector<string>>()) { }
    StrBlob(std::initializer_list<string> il):data(std::make_shared<vector<string>>(il)) { }
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    std::string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    std::string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    const std::string& front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const std::string& back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }
private:
    void check(size_type i, const string &msg) const {
        if (i >= data->size()) throw std::out_of_range(msg);
    }
private:
    std::shared_ptr<vector<string>> data;
};

主函数:

#include "ex12_02.h"
#include <iostream>
int main()
{
    const StrBlob csb{ "hello", "world", "pezy" };
    StrBlob sb{ "hello", "world", "Mooophy" };
    std::cout << csb.front() << " " << csb.back() << std::endl;
    sb.back() = "pezy";
    std::cout << sb.front() << " " << sb.back() << std::endl;
}

# Exercise 12.3

StrBlob 需要 const 版本的 push_backpop_back 吗?如需要,添加进去。否则,解释为什么不需要。

解:

不需要。 push_backpop_back 会改变对象的内容。而 const 对象是只读的,因此不需要。

# Exercise 12.4

在我们的 check 函数中,没有检查 i 是否大于 0。为什么可以忽略这个检查?

解:

因为 size_type 是一个无符号整型,当传递给 check 的参数小于 0 的时候,参数值会转换成一个正整数。

# Exercise 12.5

我们未编写接受一个 initializer_list explicit 参数的构造函数。讨论这个设计策略的优点和缺点。

解:

构造函数不是 explicit 的,意味着可以从 initializer_list 隐式转换为 StrBlob 。在 StrBlob 对象中,只有一个数据成员 data ,而 StrBlob 对象本身的含义,也是一个管理字符串的序列。因此,从 initializer_listStrBlob 的转换,在逻辑上是可行的。而这个设计策略的缺点,可能在某些地方我们确实需要 initializer_list ,而编译器仍会将之转换为 StrBlob

# Exercise 12.6

编写函数,返回一个动态分配的 intvector 。将此 vector 传递给另一个函数,这个函数读取标准输入,将读入的值保存在 vector 元素中。再将 vector 传递给另一个函数,打印读入的值。记得在恰当的时刻 delete vector

解:

#include <iostream>
#include <vector>
using std::vector;
vector<int>* alloc_vector()
{
	return new vector<int>();
}
void assign_vector(vector<int>* p)
{
	int i;
	while (std::cin >> i)
	{
		p->push_back(i);
	}
}
void print_vector(vector<int>* p)
{
	for (auto i : *p)
	{
		std::cout << i << std::endl;
	}
}
int main()
{
	auto p = alloc_vector();
	assign_vector(p);
	print_vector(p);
	delete p;
	return 0;
}

# Exercise 12.7

重做上一题,这次使用 shared_ptr 而不是内置指针。

解:

#include <iostream>
#include <vector>
#include <memory>
using std::vector;
std::shared_ptr<vector<int>> alloc_vector()
{
	return std::make_shared<vector<int>>();
}
void assign_vector(std::shared_ptr<vector<int>> p)
{
	int i;
	while (std::cin >> i)
	{
		p->push_back(i);
	}
}
void print_vector(std::shared_ptr<vector<int>> p)
{
	for (auto i : *p)
	{
		std::cout << i << std::endl;
	}
}
int main()
{
	auto p = alloc_vector();
	assign_vector(p);
	print_vector(p);
	return 0;
}

# Exercise 12.8

下面的函数是否有错误?如果有,解释错误原因。

bool b() {
	int* p = new int;
	// ...
	return p;
}

解:

有错误。 p 会被强制转换成 bool ,继而没有释放指针 p 指向的对象。

# Exercise 12.9

解释下面代码执行的结果。

int *q = new int(42), *r = new int(100);
r = q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;

解:

rq 指向 42,而之前 r 指向的 100 的内存空间并没有被释放,因此会发生内存泄漏。 r2q2 都是智能指针,当对象空间不被引用的时候会自动释放。

# Exercise 12.10

下面的代码调用了第 413 页中定义的 process 函数,解释此调用是否正确。如果不正确,应如何修改?

shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));

解:

正确。 shared_ptr<int>(p) 会创建一个临时的智能指针,这个智能指针与 p 引用同一个对象,此时引用计数为 2。当表达式结束时,临时的智能指针被销毁,此时引用计数为 1。

# Exercise 12.11

如果我们像下面这样调用 process ,会发生什么?

process(shared_ptr<int>(p.get()));

解:

这样会创建一个新的智能指针,它的引用计数为 1,这个智能指针所指向的空间与 p 相同。在表达式结束后,这个临时智能指针会被销毁,引用计数为 0,所指向的内存空间也会被释放。而导致 p 所指向的空间被释放,使得 p` 成为一个空悬指针。

# Exercise 12.12

psp 的定义如下,对于接下来的对 process 的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因:

auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);
(b) process(new int());
(c) process(p);
(d) process(shared_ptr<int>(p));

解:

  • (a) 合法。将 sp 拷贝给 process 函数的形参,在函数里面引用计数为 2,函数结束后引用计数为 1。
  • (b) 不合法。不能从内置指针隐式转换为智能指针。
  • (c) 不合法。不能从内置指针隐式转换为智能指针。
  • (d) 合法。但是智能指针和内置指针一起使用可能会出现问题,在表达式结束后智能指针会被销毁,它所指向的对象也被释放。而此时内置指针 p 依旧指向该内存空间。之后对内置指针 p 的操作可能会引发错误。

# Exercise 12.13

如果执行下面的代码,会发生什么?

auto sp = make_shared<int>();
auto p = sp.get();
delete p;

解:

智能指针 sp 所指向空间已经被释放,再对 sp 进行操作会出现错误。

# Exercise 12.14

编写你自己版本的用 shared_ptr 管理 connection 的函数。

解:

#include <iostream>
#include <memory>
#include <string>
struct connection
{
	std::string ip;
	int port;
	connection(std::string i, int p) : ip(i), port(p) {}
};
struct destination
{
	std::string ip;
	int port;
	destination(std::string i, int p) : ip(i), port(p) {}
};
connection connect(destination* pDest)
{
	std::shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
	std::cout << "creating connection(" << pConn.use_count() << ")" << std::endl;
	return *pConn;
}
void disconnect(connection pConn)
{
	std::cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << std::endl;	
}
void end_connection(connection* pConn)
{
	disconnect(*pConn);
}
void f(destination &d)
{
	connection conn = connect(&d);
	std::shared_ptr<connection> p(&conn, end_connection);
	std::cout << "connecting now(" << p.use_count() << ")" << std::endl;
}
int main()
{
	destination dest("220.181.111.111", 10086);
	f(dest);
	return 0;
}

# Exercise 12.15

重写上一题的程序,用 lambda 代替 end_connection 函数。

解:

#include <iostream>
#include <memory>
#include <string>
struct connection
{
	std::string ip;
	int port;
	connection(std::string i, int p) : ip(i), port(p) {}
};
struct destination
{
	std::string ip;
	int port;
	destination(std::string i, int p) : ip(i), port(p) {}
};
connection connect(destination* pDest)
{
	std::shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
	std::cout << "creating connection(" << pConn.use_count() << ")" << std::endl;
	return *pConn;
}
void disconnect(connection pConn)
{
	std::cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << std::endl;
}
void f(destination &d)
{
	connection conn = connect(&d);
	std::shared_ptr<connection> p(&conn, [] (connection* p){ disconnect(*p); });
	std::cout << "connecting now(" << p.use_count() << ")" << std::endl;
}
int main()
{
	destination dest("220.181.111.111", 10086);
	f(dest);
	return 0;
}

# Exercise 12.16

如果你试图拷贝或赋值 unique_ptr ,编译器并不总是能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。

解:

#include <iostream>
#include <string>
#include <memory>
using std::string; using std::unique_ptr;
int main()
{
    unique_ptr<string> p1(new string("pezy"));
    // unique_ptr<string> p2(p1); // copy
    //                      ^
    // Error: Call to implicitly-deleted copy constructor of 'unique_ptr<string>'
    //
    // unique_ptr<string> p3 = p1; // assign
    //                      ^
    // Error: Call to implicitly-deleted copy constructor of 'unique_ptr<string>'
    std::cout << *p1 << std::endl;
    p1.reset(nullptr);
}

# Exercise 12.17

下面的 unique_ptr 声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。

int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;
(a) IntP p0(ix);
(b) IntP p1(pi);
(c) IntP p2(pi2);
(d) IntP p3(&ix);
(e) IntP p4(new int(2048));
(f) IntP p5(p2.get());

解:

  • (a) 不合法。在定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上。
  • (b) 不合法。理由同上。
  • (c) 合法。但是也可能会使得 pi2 成为空悬指针。
  • (d) 不合法。当 p3 被销毁时,它试图释放一个栈空间的对象。
  • (e) 合法。
  • (f) 不合法。 p5p2 指向同一个对象,当 p5p2 被销毁时,会使得同一个指针被释放两次。

# Exercise 12.18

shared_ptr 为什么没有 release 成员?

release 成员的作用是放弃控制权并返回指针,因为在某一时刻只能有一个 unique_ptr 指向某个对象, unique_ptr 不能被赋值,所以要使用 release 成员将一个 unique_ptr 的指针的所有权传递给另一个 unique_ptr 。而 shared_ptr 允许有多个 shared_ptr 指向同一个对象,因此不需要 release 成员。

# Exercise 12.19

定义你自己版本的 StrBlobPtr ,更新 StrBlob 类,加入恰当的 friend 声明以及 beginend 成员。

解:

#include <string>
#include <vector>
#include <initializer_list>
#include <memory>
#include <stdexcept>
using std::vector; using std::string;
class StrBlobPtr;
class StrBlob
{
public:
	using size_type = vector<string>::size_type;
	friend class StrBlobPtr;
	StrBlobPtr begin();
	StrBlobPtr end();
	StrBlob() : data(std::make_shared<vector<string>>()) {}
	StrBlob(std::initializer_list<string> il) : data(std::make_shared<vector<string>>(il)) {}
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	void push_back(const string& s) { data->push_back(s); }
	void pop_back()
	{
		check(0, "pop_back on empty StrBlob");
		data->pop_back();
	}
	std::string& front()
	{
		check(0, "front on empty StrBlob");
		return data->front();
	}
	std::string& back()
	{
		check(0, "back on empty StrBlob");
		return data->back();
	}
	const std::string& front() const
	{
		check(0, "front on empty StrBlob");
		return data->front();
	}
	const std::string& back() const
	{
		check(0, "back on empty StrBlob");
		return data->back();
	}
private:
	void check(size_type i, const string& msg) const
	{
		if (i >= data->size())
			throw std::out_of_range(msg);
	}
private:
	std::shared_ptr<vector<string>> data;
};
class StrBlobPtr
{
public:
	StrBlobPtr() :curr(0) {}
	StrBlobPtr(StrBlob &a, size_t sz = 0) :wptr(a.data), curr(sz) {}
	bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }
	string& deref() const
	{
		auto p = check(curr, "dereference past end");
		return (*p)[curr];
	}
	StrBlobPtr& incr()
	{
		check(curr, "increment past end of StrBlobPtr");
		++curr;
		return *this;
	}
private:
	std::shared_ptr<vector<string>> check(size_t i, const string &msg) const
	{
		auto ret = wptr.lock();
		if (!ret) throw std::runtime_error("unbound StrBlobPtr");
		if (i >= ret->size()) throw std::out_of_range(msg);
		return ret;
	}
	std::weak_ptr<vector<string>> wptr;
	size_t curr;
};
StrBlobPtr StrBlob::begin()
{
	return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end()
{
	return StrBlobPtr(*this, data->size());
}

# Exercise 12.20

编写程序,逐行读入一个输入文件,将内容存入一个 StrBlob 中,用一个 StrBlobPtr 打印出 StrBlob 中的每个元素。

解:

#include <iostream>
#include <fstream>
#include "exercise12_19.h"
using namespace std;
int main()
{
	ifstream ifs("books.txt");
	StrBlob sb;
	string s;
	while (getline(ifs, s))
	{
		sb.push_back(s);
	}
	for (StrBlobPtr sbp = sb.begin(); sbp != sb.end(); sbp.incr())
	{
		cout << sbp.deref() << endl;
	}
	return 0;
}

# Exercise 12.21

也可以这样编写 StrBlobPtrderef 成员:

std::string& deref() const {
	return (*check(curr, "dereference past end"))[curr];
}

你认为哪个版本更好?为什么?

解:

原来的版本更好,可读性更高。

# Exercise 12.22

为了能让 StrBlobPtr 使用 const StrBlob ,你觉得应该如何修改?定义一个名为 ConstStrBlobPtr 的类,使其能够指向 const StrBlob

解:

构造函数改为接受 const Strblob & , 然后给 Strblob 类添加两个 const 成员函数 cbegincend ,返回 ConstStrBlobPtr

# Dynamic Arrays


# 动态数组

# new 和数组

  • new 一个动态数组:

    • 类型名之后加一对方括号,指明分配的对象数目(必须是整型,不必是常量)。
    • 返回指向第一个对象的指针
    • int *p = new int[size];
  • delete 一个动态数组:

    • delete [] p;
  • unique_ptr 和数组:

    • 指向数组的 unique_ptr 不支持成员访问运算符(点和箭头)。
操作解释
unique_ptr<T[]> uu 可以指向一个动态分配的数组,整数元素类型为 T
unique_ptr<T[]> u(p)u 指向内置指针 p 所指向的动态分配的数组。 p 必须能转换为类型 T*
u[i]返回 u 拥有的数组中位置 i 处的对象。 u 必须指向一个数组。

# allocator 类

  • 标准库 allocator 类定义在头文件 memory 中,帮助我们将内存分配和对象构造分离开。
  • 分配的是原始的、未构造的内存。
  • allocator 是一个模板。
  • allocator<string> alloc;

标准库 allocator 类及其算法

操作解释
allocator<T> a定义了一个名为 aallocator 对象,它可以为类型为 T 的对象分配内存
a.allocate(n)分配一段原始的、未构造的内存,保存 n 个类型为 T 的对象。
a.deallocate(p, n)释放从 T* 指针 p 中地址开始的内存,这块内存保存了 n 个类型为 T 的对象; p 必须是一个先前由 allocate 返回的指针。且 n 必须是 p 创建时所要求的大小。在调用 deallocate 之前,用户必须对每个在这块内存中创建的对象调用 destroy
a.construct(p, args)p 必须是一个类型是 T* 的指针,指向一块原始内存; args 被传递给类型为 T 的构造函数,用来在 p 指向的内存中构造一个对象。
a.destroy(p)pT* 类型的指针,此算法对 p 指向的对象执行析构函数。

allocator 伴随算法

操作解释
uninitialized_copy(b, e, b2)从迭代器 be 给定的输入范围中拷贝元素到迭代器 b2 指定的未构造的原始内存中。 b2 指向的内存必须足够大,能够容纳输入序列中元素的拷贝。
uninitialized_copy_n(b, n, b2)从迭代器 b 指向的元素开始,拷贝 n 个元素到 b2 开始的内存中。
uninitialized_fill(b, e, t)在迭代器 be 执行的原始内存范围中创建对象,对象的值均为 t 的拷贝。
uninitialized_fill_n(b, n, t)从迭代器 b 指向的内存地址开始创建 n 个对象。 b 必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。
  • 定义在头文件 memory 中。
  • 在给定目的位置创建元素,而不是由系统分配内存给他们。

# Exercise 12.23

编写一个程序,连接两个字符串字面常量,将结果保存在一个动态分配的 char 数组中。重写这个程序,连接两个标准库 string 对象。

解:

#include <iostream>
#include <string>
#include <cstring>
#include <memory>
int main() {
	const char *c1 = "Hello ";
	const char *c2 = "World";
	unsigned len = strlen(c1) + strlen(c2) + 1;
	char *r = new char[len]();
	strcat_s(r, len, c1);
	strcat_s(r, len, c2);
	std::cout << r << std::endl;
	std::string s1 = "Hello ";
	std::string s2 = "World";
	strcpy_s(r, len, (s1 + s2).c_str());
	std::cout << r << std::endl;
	delete[] r;
	return 0;
}

# Exercise 12.24

编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。

解:

#include <iostream>
int main()
{
	std::cout << "How long do you want the string? ";
	int size{ 0 };
	std::cin >> size;
	char *input = new char[size + 1]();
	std::cin.ignore();
	std::cout << "input the string: ";
	std::cin.get(input, size + 1);
	std::cout << input;
	delete[] input;
	return 0;
}

# Exercise 12.25

给定下面的 new 表达式,你应该如何释放 pa

int *pa = new int[10];

解:

delete [] pa;

# Exercise 12.26

allocator 重写第 427 页中的程序。

#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
	int n = 5;
	allocator<string> alloc;
	auto p = alloc.allocate(n);
	string s;
	auto q = p;
	while (cin >> s && q != p + n)
	{
		alloc.construct(q++, s);
	}
	while (q != p)
	{
		std::cout << *--q << " ";
		alloc.destroy(q);
	}
	alloc.deallocate(p, n);
	return 0;
}

# Using the Library: A Text-Query Program


# Exercise 12.27

TextQueryQueryResult 类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。

解:

头文件:

#ifndef EX12_27_H
#define EX12_27_H
#include <fstream>
#include <memory>
#include <vector>
#include <string>
#include <map>
#include <set>
class QueryResult;
class TextQuery
{
public:
	using line_no = std::vector<std::string>::size_type;
	TextQuery(std::ifstream&);
	QueryResult query(const std::string& s) const;
private:
	std::shared_ptr<std::vector<std::string>> file;
	std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};
class QueryResult
{
public:
	friend std::ostream& print(std::ostream&, const QueryResult&);
	QueryResult(std::string s,
				std::shared_ptr<std::set<TextQuery::line_no>> p,
				std::shared_ptr<std::vector<std::string>> f) :
		sought(s), lines(p), file(f) 
	{}
private:
	std::string sought;
	std::shared_ptr<std::set<TextQuery::line_no>> lines;
	std::shared_ptr<std::vector<std::string>> file;
};
std::ostream& print(std::ostream&, const QueryResult&);
#endif

实现:

#include "ex_12_27.h"
#include <sstream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;
TextQuery::TextQuery(ifstream& ifs) : file(new vector<string>)
{
	string text;
	while (getline(ifs, text))
	{
		file->push_back(text);
		int n = file->size() - 1;
		istringstream line(text);
		string word;
		while (line >> word)
		{
			auto &lines = wm[word];
			if (!lines)
				lines.reset(new set<line_no>);
			lines->insert(n);
		}
	}
}
QueryResult TextQuery::query(const string& s) const
{
	static shared_ptr<set<line_no>> nodata(new set<line_no>);
	auto loc = wm.find(s);
	if (loc == wm.end())
		return QueryResult(s, nodata, file);
	else
		return QueryResult(s, loc->second, file);
}
std::ostream& print(std::ostream& os, const QueryResult& qr)
{
	os << qr.sought << " occurs " << qr.lines->size() << " "
		<< "time" << (qr.lines->size() > 1 ? "s" : "") << endl;
	for (auto num : *qr.lines)
		os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << endl;
	return os;
}

主函数:

#include <iostream>
#include <string>
#include <fstream>
#include "ex_12_27.h"
using namespace std;
void runQueries(ifstream& infile)
{
	TextQuery tq(infile);
	while (true)
	{
		cout << "enter word to look for, or q to quit: ";
		string s;
		if (!(cin >> s) || s == "q") break;
		print(cout, tq.query(s)) << endl;
	}
}
int main()
{
	ifstream ifs("storyDataFile.txt");
	runQueries(ifs);
	return 0;
}

# Exercise 12.28

编写程序实现文本查询,不要定义类来管理数据。你的程序应该接受一个文件,并与用户交互来查询单词。使用 vectormapset 容器来保存来自文件的数据并生成查询结果。

解:

#include <string>
using std::string;
#include <vector>
using std::vector;
#include <memory>
using std::shared_ptr;
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <set>
#include <algorithm>
int main()
{
	std::ifstream file("H:/code/C++/Cpp_Primer_Answers/data/storyDataFile.txt");
	vector<string> input;
	std::map<string, std::set<decltype(input.size())>> dictionary;
	decltype(input.size()) lineNo{ 0 };
	for (string line; std::getline(file, line); ++lineNo)
	{
		input.push_back(line);
		std::istringstream line_stream(line);
		for (string text, word; line_stream >> text; word.clear())
		{
			std::remove_copy_if(text.begin(), text.end(), std::back_inserter(word), ispunct);
			dictionary[word].insert(lineNo);
		}
	}
	while (true)
	{
		std::cout << "enter word to look for, or q to quit: ";
		string s;
		if (!(std::cin >> s) || s == "q") break;
		auto found = dictionary.find(s);
		if (found != dictionary.end())
		{
			std::cout << s << " occurs " << found->second.size() << (found->second.size() > 1 ? " times" : " time") << std::endl;
			for (auto i : found->second)
				std::cout << "\t(line " << i + 1 << ") " << input.at(i) << std::endl;
		}
		else std::cout << s << " occurs 0 time" << std::endl;
	}
}

# Exercise 12.29

我们曾经用 do while 循环来编写管理用户交互的循环。用 do while 重写本节程序,解释你倾向于哪个版本,为什么?

解:

do {
    std::cout << "enter word to look for, or q to quit: ";
    string s;
    if (!(std::cin >> s) || s == "q") break;
    print(std::cout, tq.query(s)) << std::endl;
} while ( true );

我更喜欢 while ,这可能是习惯的问题。

# Exercise 12.30

定义你自己版本的 TextQueryQueryResult 类,并执行 12.3.1 节中的 runQueries 函数。

解:

同 12.27。

# Exercise 12.31

如果用 vector 代替 set 保存行号,会有什么差别?哪个方法更好?为什么?

如果用 vector 则会有单词重复的情况出现。而这里保存的是行号,不需要重复元素,所以 set 更好。

# Exercise 12.32

重写 TextQueryQueryResult 类,用 StrBlob 代替 vector<string> 保存输入文件。

解:

TextQueryQueryResult 类中的 file 成员,改为 指向 StrBlob 的智能指针。在访问 StrBlob 时,要使用 StrBlobPtr

# Exercise 12.33

在第 15 章中我们将扩展查询系统,在 QueryResult 类中将会需要一些额外的成员。添加名为 beginend 的成员,返回一个迭代器,指向一个给定查询返回的行号的 set 中的位置。再添加一个名为 get_file 的成员,返回一个 shared_ptr ,指向 QueryResult 对象中的文件。

解:

class QueryResult{
public:
	using Iter = std::set<line_no>::iterator;	
	// ...
	Iter begin() const { return lines->begin(); }
	Iter end() const { return lines->end(); }
	shared_ptr<std::vector<std::string>> get_file() const 
	{ 
		return std::make_shared<std::vector<std::string>>(file); 
	}
private:
	// ...
};

# Chapter Summary

🍓:)