# 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 的对象 |
p | 将 p 用作一个条件判断,若 p 指向一个对象,则为 true |
*p | 解引用 p ,获得它指向的对象。 |
p->mem | 等价于 (*p).mem |
p.get() | 返回 p 中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。 |
swap(p, q) p.swap(q) | 交换 p 和 q 中的指针 |
shared_ptr 独有的操作:
操作 | 解释 |
---|---|
make_shared<T>(args) | 返回一个 shared_ptr ,指向一个动态分配的类型为 T 的对象。使用 args 初始化此对象。 |
shared_ptr<T>p(q) | p 是 shared_ptr q 的拷贝;此操作会递增 q 中的计数器。 q 中的指针必须能转换为 T* |
p = q | p 和 q 都是 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)。
- 使用
new
和delete
管理动态内存存在三个常见问题:- 1. 忘记
delete
内存。 - 2. 使用已经释放掉的对象。
- 3. 同一块内存释放两次。
- 1. 忘记
- 坚持只使用智能指针可以避免上述所有问题。
# shared_ptr 和 new 结合使用
定义和改变 shared_ptr 的其他方法:
操作 | 解释 |
---|---|
shared_ptr<T> p(q) | p 管理内置指针 q 所指向的对象; q 必须指向 new 分配的内存,且能够转换为 T* 类型 |
shared_ptr<T> p(u) | p 从 unique_ptr u 那里接管了对象的所有权;将 u 置为空 |
shared_ptr<T> p(q, d) | p 接管了内置指针 q 所指向的对象的所有权。 q 必须能转换为 T* 类型。 p 将使用可调用对象 d 来代替 delete 。 |
shared_ptr<T> p(p2, d) | p 是 shared_ptr p2 的拷贝,唯一的区别是 p 将可调用对象 d 来代替 delete 。 |
p.reset() | 若 p 是唯一指向其对象的 shared_ptr , reset 会释放此对象。若传递了可选的参数内置指针 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> u1 | 空 unique_ptr ,可以指向类型是 T 的对象。 u1 会使用 delete 来是释放它的指针。 |
unique_ptr<T, D> u2 | u2 会使用一个类型为 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> w | 空 weak_ptr 可以指向类型为 T 的对象 |
weak_ptr<T> w(sp) | 与 shared_ptr 指向相同对象的 weak_ptr 。 T 必须能转换为 sp 指向的类型。 |
w = p | p 可以是 shared_ptr 或一个 weak_ptr 。赋值后 w 和 p 共享对象。 |
w.reset() | 将 w 置为空。 |
w.use_count() | 与 w 共享对象的 shared_ptr 的数量。 |
w.expired() | 若 w.use_count() 为 0,返回 true ,否则返回 false |
w.lock() | 如果 expired 为 true ,则返回一个空 shared_ptr ;否则返回一个指向 w 的对象的 shared_ptr 。 |
# Exercise 12.1
在此代码的结尾,
b1
和b2
各包含多少个元素?
StrBlob b1; | |
{ | |
StrBlob b2 = {"a", "an", "the"}; | |
b1 = b2; | |
b2.push_back("about"); | |
} |
解:
它们实际操作的是同一个 vector
,都包含 4 个元素。在代码的结尾, b2
被析构了,不影响 b1
的元素。
# Exercise 12.2
编写你自己的
StrBlob
类,包含const
版本的front
和back
。
解:
头文件:
#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_back
和pop_back
吗?如需要,添加进去。否则,解释为什么不需要。
解:
不需要。 push_back
和 pop_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_list
到 StrBlob
的转换,在逻辑上是可行的。而这个设计策略的缺点,可能在某些地方我们确实需要 initializer_list
,而编译器仍会将之转换为 StrBlob
。
# Exercise 12.6
编写函数,返回一个动态分配的
int
的vector
。将此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; |
解:
r
和 q
指向 42,而之前 r
指向的 100 的内存空间并没有被释放,因此会发生内存泄漏。 r2
和 q2
都是智能指针,当对象空间不被引用的时候会自动释放。
# 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
p
和sp
的定义如下,对于接下来的对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) 不合法。
p5
和p2
指向同一个对象,当p5
和p2
被销毁时,会使得同一个指针被释放两次。
# 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
声明以及begin
和end
成员。
解:
#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
也可以这样编写
StrBlobPtr
的deref
成员:
std::string& deref() const { | |
return (*check(curr, "dereference past end"))[curr]; | |
} |
你认为哪个版本更好?为什么?
解:
原来的版本更好,可读性更高。
# Exercise 12.22
为了能让
StrBlobPtr
使用const StrBlob
,你觉得应该如何修改?定义一个名为ConstStrBlobPtr
的类,使其能够指向const StrBlob
。
解:
构造函数改为接受 const Strblob &
, 然后给 Strblob
类添加两个 const
成员函数 cbegin
和 cend
,返回 ConstStrBlobPtr
。
# Dynamic Arrays
# 动态数组
# new 和数组
new
一个动态数组:- 类型名之后加一对方括号,指明分配的对象数目(必须是整型,不必是常量)。
- 返回指向第一个对象的指针。
int *p = new int[size];
delete
一个动态数组:delete [] p;
unique_ptr
和数组:- 指向数组的
unique_ptr
不支持成员访问运算符(点和箭头)。
- 指向数组的
操作 | 解释 |
---|---|
unique_ptr<T[]> u | u 可以指向一个动态分配的数组,整数元素类型为 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 | 定义了一个名为 a 的 allocator 对象,它可以为类型为 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) | p 为 T* 类型的指针,此算法对 p 指向的对象执行析构函数。 |
allocator 伴随算法:
操作 | 解释 |
---|---|
uninitialized_copy(b, e, b2) | 从迭代器 b 和 e 给定的输入范围中拷贝元素到迭代器 b2 指定的未构造的原始内存中。 b2 指向的内存必须足够大,能够容纳输入序列中元素的拷贝。 |
uninitialized_copy_n(b, n, b2) | 从迭代器 b 指向的元素开始,拷贝 n 个元素到 b2 开始的内存中。 |
uninitialized_fill(b, e, t) | 在迭代器 b 和 e 执行的原始内存范围中创建对象,对象的值均为 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
TextQuery
和QueryResult
类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。
解:
头文件:
#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
编写程序实现文本查询,不要定义类来管理数据。你的程序应该接受一个文件,并与用户交互来查询单词。使用
vector
、map
和set
容器来保存来自文件的数据并生成查询结果。
解:
#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
定义你自己版本的
TextQuery
和QueryResult
类,并执行 12.3.1 节中的runQueries
函数。
解:
同 12.27。
# Exercise 12.31
如果用
vector
代替set
保存行号,会有什么差别?哪个方法更好?为什么?
如果用 vector
则会有单词重复的情况出现。而这里保存的是行号,不需要重复元素,所以 set
更好。
# Exercise 12.32
重写
TextQuery
和QueryResult
类,用StrBlob
代替vector<string>
保存输入文件。
解:
TextQuery
和 QueryResult
类中的 file
成员,改为 指向 StrBlob
的智能指针。在访问 StrBlob
时,要使用 StrBlobPtr
。
# Exercise 12.33
在第 15 章中我们将扩展查询系统,在
QueryResult
类中将会需要一些额外的成员。添加名为begin
和end
的成员,返回一个迭代器,指向一个给定查询返回的行号的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
🍓:)