C++Primer 第十二章 动态内存
12.1 动态内存和智能指针
12.1.1 shared_ptr类
12.1 在此代码的结尾,b1 和 b2 各包含多少个元素?
StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
}
// b1包含4个元素;
// b2被销毁。
12.2 编写你自己的StrBlob 类,包含const 版本的 front 和 back。
#ifndef STRBLOB_H
#define STRBLOB_H
#include <memory>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
class StrBlob
{
public:
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list<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();
string &front();
string &back();
string &front() const;
string &back() const;
private:
shared_ptr<vector<string>> data;
void check(size_type i, const string &msg) const;
};
StrBlob::StrBlob() : data(make_shared<vector<string>>()){ }
StrBlob::StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) {}
void StrBlob::check(size_type i, const string &msg) const
{
if (i >= data->size())
throw out_of_range(msg);
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
string &StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
string &StrBlob::front() const
{
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back() const
{
check(0, "back on empty StrBlob");
return data->back();
}
#endif
12.3 StrBlob 需要const 版本的push_back 和 pop_back吗?如需要,添加进去。否则,解释为什么不需要。
不需要,添加进去虽然编译器不会报错,但是这样不符合类使用者的使用习惯。
12.4 在我们的 check 函数中,没有检查 i 是否大于0。为什么可以忽略这个检查?
可以忽略,本身i就是大于0的。
12.5 我们未编写接受一个 initializer_list explicit 参数的构造函数。讨论这个设计策略的优点和缺点。
使用explicit之后
优点:我们可以清楚地知道使用的是哪种类型;
缺点:不易使用,需要显示地初始化。
12.1.2 直接内存管理
12.6 编写函数,返回一个动态分配的 int 的vector。将此vector 传递给另一个函数,这个函数读取标准输入,将读入的值保存在 vector 元素中。再将vector传递给另一个函数,打印读入的值。记得在恰当的时刻delete vector。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> *loc_vec()
{
return new vector<int>();
}
void read(istream &is, vector<int> *vec)
{
int i = 0;
while (is >> i)
vec->push_back(i);
}
void print(ostream &os, const vector<int> *vec)
{
for(auto i : *vec)
os << i << " ";
os << endl;
}
int main()
{
vector<int> *vp = loc_vec();
read(cin, vp);
print(cout, vp);
delete vp;
return 0;
}
12.7 重做上一题,这次使用 shared_ptr 而不是内置指针。
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
using namespace std;
shared_ptr<vector<int>> loc_vec()
{
return make_shared<vector<int>>();
}
void read(istream &is, shared_ptr<vector<int>> vec)
{
int i = 0;
while (is >> i)
vec->push_back(i);
}
void print(ostream &os, shared_ptr<vector<int>> vec)
{
for(auto i : *vec)
os << i << " ";
os << endl;
}
int main()
{
auto vp = loc_vec();
read(cin, vp);
print(cout, vp);
return 0;
}
12.8 下面的函数是否有错误?如果有,解释错误原因。
bool b() {
int* p = new int;
// ...
return p;
}
返回p是一个指针,而不是一个bool
12.9 解释下面代码执行的结果。
int *q = new int(42), *r = new int(100);
r = q;// r=q后r所指的内存没有释放,应该先delete r,再r=q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;// 第二段代码内存会自动释放。
12.1.3 shared_ptr和new结合使用
12.10 下面的代码调用了第413页中定义的process 函数,解释此调用是否正确。如果不正确,应如何修改?
shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));
// 正确
12.11 如果我们像下面这样调用 process,会发生什么?
process(shared_ptr<int>(p.get()));
// 离开process时,p指向的内存会被释放,再使用p指针时会出现错误。
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)合法,将智能指针赋值给process;
// (b)不合法,shared_ptr初始化内置指针时需要使用直接初始化的形式;
// (c)不合法,shared_ptr初始化内置指针时需要使用直接初始化的形式;
// (d)合法。
12.13 如果执行下面的代码,会发生什么?
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
12.1.4 智能指针和异常
12.14 编写你自己版本的用 shared_ptr 管理 connection 的函数。
12.15 重写第一题的程序,用 lambda 代替end_connection 函数。
#include <iostream>
#include <string>
#include <memory>
#include <algorithm>
using namespace std;
struct destination{};
struct connection{};
connection connect(destination *){};
void disconnect(connection){};
// void end_connection(connection *p)
// {
// disconnect(*p);
// }
void f(destination &d)
{
connection c = connect(&d);
// shared_ptr<connection> p(&c, end_connection);
shared_ptr<connection> p(&c, [](connection *p){disconnect(*p);});
}
int main()
{
return 0;
}
12.1.5 unique_ptr
12.16 如果你试图拷贝或赋值 unique_ptr,编译器并不总是能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> p(new int(10));
// unique_ptr<int> p1 = p;
// unique_ptr<int> p2(p);
unique_ptr<int> p3(p.release());
cout << *p3 << endl;
return 0;
}
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)非法,初始化错误;
(b)编译时合法,运行时会报错,因为pi不是new出来的,销毁时使用默认的delete会出错;
(c)编译时合法,但是运行时会导致空悬指针,unique_ptr释放空间时,使用pi2指针会出错;
(d)编译时合法,运行时会报错,因为指针不是new出来的,销毁时使用默认的delete会出错;
(e)合法;
(f)编译时合法,但是会导致两次delete或者一个delete后另一个变为空悬指针。
12.18 shared_ptr 为什么没有 release 成员?
多个shared_ptr可以指向同一个对象,直接赋值即可,无需release成员。
12.1.6 weak_ptr
12.19 定义你自己版本的 StrBlobPtr,更新 StrBlob 类,加入恰当的 friend 声明以及 begin 和 end 成员。
#ifndef STRBLOB_H
#define STRBLOB_H
#include <memory>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
class StrBlobPtr;
class StrBlob
{
public:
friend class StrBlobPtr;
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list<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();
string &front();
string &back();
string &front() const;
string &back() const;
StrBlobPtr begin();
StrBlobPtr end();
private:
shared_ptr<vector<string>> data;
void check(size_type i, const string &msg) const;
};
// 对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
string &deref() const; // 解引用
StrBlobPtr &incr(); // 前置递增
private:
shared_ptr<vector<string>> check(size_t, const string &) const;
weak_ptr<vector<string>> wptr;
size_t curr;
};
StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
void StrBlob::check(size_type i, const string &msg) const
{
if (i >= data->size())
throw out_of_range(msg);
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
string &StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
string &StrBlob::front() const
{
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back() const
{
check(0, "back on empty StrBlob");
return data->back();
}
shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
auto ret = wptr.lock();
if (!ret)
throw runtime_error("unbound StrBlobStr");
if (i >= ret->size())
throw out_of_range(msg);
return ret;
}
string &StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
StrBlobPtr &StrBlobPtr::incr()
{
auto p = check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
#endif
12.20 编写程序,逐行读入一个输入文件,将内容存入一个 StrBlob 中,用一个 StrBlobPtr 打印出 StrBlob 中的每个元素。
#include <iostream>
#include <string>
#include <fstream>
#include "StrBlob.h"
using namespace std;
int main()
{
ifstream in("file3");
StrBlob b1;
string s;
while (getline(in, s))
b1.push_back(s);
for(StrBlobPtr beg = b1.begin(), ed = b1.end(); beg != ed; beg.incr())
std::cout << beg.deref() << std::endl;
return 0;
}
12.21 也可以这样编写 StrBlobPtr 的 deref 成员:
std::string& deref() const {
return (*check(curr, "dereference past end"))[curr];
}
你认为哪个版本更好?为什么?
之前的版本更好,更容易读懂。
12.22 为了能让 StrBlobPtr 使用 const StrBlob,你觉得应该如何修改?定义一个名为ConstStrBlobPtr 的类,使其能够指向 const StrBlob。
#ifndef STRBLOB_H
#define STRBLOB_H
#include <memory>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
class StrBlobPtr;
class StrBlob
{
public:
friend class StrBlobPtr;
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list<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();
string &front();
string &back();
string &front() const;
string &back() const;
StrBlobPtr begin();
StrBlobPtr end();
private:
shared_ptr<vector<string>> data;
void check(size_type i, const string &msg) const;
};
// 对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
string &deref() const; // 解引用
StrBlobPtr &incr(); // 前置递增
bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }
private:
shared_ptr<vector<string>> check(size_t, const string &) const;
weak_ptr<vector<string>> wptr;
size_t curr;
};
StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
void StrBlob::check(size_type i, const string &msg) const
{
if (i >= data->size())
throw out_of_range(msg);
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
string &StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
string &StrBlob::front() const
{
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back() const
{
check(0, "back on empty StrBlob");
return data->back();
}
shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
auto ret = wptr.lock();
if (!ret)
throw runtime_error("unbound StrBlobStr");
if (i >= ret->size())
throw out_of_range(msg);
return ret;
}
string& StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
StrBlobPtr& StrBlobPtr::incr()
{
auto p = check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
#endif
12.2 动态数组
12.2.1 new和数组
12.23 编写一个程序,连接两个字符串字面常量,将结果保存在一个动态分配的char数组中。重写这个程序,连接两个标准库string对象。
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int main()
{
// const char* a = "aaa";
// const char* b = "bbb";
// char* cp = new char[strlen(a)+strlen(b)];
// strcat(cp, a);
// strcat(cp, b);
// cout << string(cp) << endl;
// delete [] cp;
string s1, s2;
cin >> s1 >> s2;
string* cp = new string;
*cp = s1 + s2;
cout << *cp << endl;
delete cp;
return 0;
}
12.24 编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。
#include <iostream>
using namespace std;
int main()
{
string s;
cin >> s;
size_t len = s.size();
char* cp = new char[len];
for (size_t i = 0; i < len; ++i)
{
cp[i] = s[i];
cout << cp[i] << endl;
}
delete [] cp;
return 0;
}
12.25 给定下面的new表达式,你应该如何释放pa?
int *pa = new int[10];
delete [] pa;
12.2.2 allocator类
12.26 用 allocator 重写第427页中的程序。
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
allocator<string> alloc;
string s;
int n; cin >> n;
auto const p = alloc.allocate(n);
auto q = p;
cout << "请输入字符: " << endl;
while (cin >> s && q != p+n)
alloc.construct(q++, s);
while (q != p)
{
cout << *(--q) << endl;
alloc.destroy(q);
}
alloc.deallocate(p, n);
return 0;
}
12.3 使用标准库:文本查询程序
12.3.1 文本查询程序设计
12.27 TextQuery 和 QueryResult 类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。
12.28 编写程序实现文本查询,不要定义类来管理数据。你的程序应该接受一个文件,并与用户交互来查询单词。使用vector、map 和 set 容器来保存来自文件的数据并生成查询结果。
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <sstream>
#include <set>
#include <memory>
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
class QueryResult;
class TextQuery
{
public:
using line_no = vector<string>::size_type;
TextQuery(ifstream&);
QueryResult query(const string&) const;
private:
shared_ptr<vector<string>> file;
map<string, shared_ptr<set<line_no>>> wm;
};
class QueryResult
{
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
QueryResult(string s, shared_ptr<set<TextQuery::line_no>> p, shared_ptr<vector<string>> f) : sought(s), lines(p), file(f) {}
private:
string sought;
shared_ptr<set<TextQuery::line_no>> lines;
shared_ptr<vector<string>> file;
};
TextQuery::TextQuery(ifstream& ifs) : file(new vector<string>)
{
string text;
while (getline(ifs, text))
{
file->push_back(text);
int n = file->size();
istringstream line(text);
string word;
while (line >> word)
{
auto &lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
// for (auto &m : wm)
// {
// cout << m.first << " : ";
// for (auto l : *m.second)
// cout << l << " ";
// cout << endl;
// }
}
QueryResult TextQuery::query(const string& sought) const
{
shared_ptr<set<TextQuery::line_no>> nodate(new set<TextQuery::line_no>);
auto loc = wm.find(sought);
if (loc == wm.end())
return QueryResult(sought, nodate, file);
else
return QueryResult(sought, loc->second, file);
}
std::ostream &print(std::ostream &os, const QueryResult &qr)
{
os << qr.sought << " occurs " << qr.lines->size() << " " /*<< make_plural(qr.lines->size(), "time", "s")*/ << std::endl;
for(auto num : *qr.lines)
os << "\t(line " << num << ") " << std::endl;
return os;
}
int main()
{
ifstream in("file1");
TextQuery tq(in);
while (true)
{
cout << "enter word to look for, or q to quite: ";
string s;
if (!(cin >> s) || s == "q") break;
print(cout, tq.query(s));
}
return 0;
}
12.29 我们曾经用do while 循环来编写管理用户交互的循环。用do while 重写本节程序,解释你倾向于哪个版本,为什么?
do
{
cout << "enter word to look for, or q to quite: ";
string s;
if (!(cin >> s) || s == "q") break;
print(cout, tq.query(s));
}while (true);
while 版本更加简洁明了
12.3.2 文本查询程序类的定义
12.30 定义你自己版本的 TextQuery 和 QueryResult 类,并执行12.3.1节中的runQueries 函数。
答案同12.28;
12.31 如果用vector 代替 set 保存行号,会有什么差别?哪个方法更好?为什么?
如果用 vector 则会有单词重复的情况出现。而这里保存的是行号,不需要重复元素,所以 set 更好。
12.32 重写 TextQuery 和 QueryResult类,用StrBlob 代替 vector 保存输入文件。
TextQuery 和 QueryResult 类中的 file 成员,改为 指向 StrBlob 的智能指针。在访问 StrBlob 时,要使用 StrBlobPtr。
12.33 在第15章中我们将扩展查询系统,在 QueryResult 类中将会需要一些额外的成员。添加名为 begin 和 end 的成员,返回一个迭代器,指向一个给定查询返回的行号的 set 中的位置。再添加一个名为 get_file 的成员,返回一个 shared_ptr,指向 QueryResult 对象中的文件。
using iter = set<TextQuery::line_no>::iterator;
iter begin() const {return lines->begin();}
iter end() const {return lines->end();}
shared_ptr<vector<string>> get_file() {return make_shared<vector<string>>(file);}