笔记:
12.1 动态内存与智能指针
使用动态内存的三种原因:
1.程序不知道自己需要使用多少对象——容器
2.程序不知道所需对象的准确类型
3.程序需要在多个对象间共享数据——
内存泄漏:忘记delete内存。
接受指针参数的智能指针构造函数是explicit的,因此必须使用直接初始化,用“=”是拷贝初始化,是不行的。
永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。
传递给delete的指针必须指向动态分配的内存,或者是一个空指针。
unique_ptr不支持普通的拷贝和赋值操作。
12.2 动态数组
大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单,更不容易出现内存管理错误并且有可能有更好的性能。
分配一个数组会得到一个元素类型的指针。
delete 动态数组指针时,一定要记得加上方括号[ ]。
shared_ptr未定义下标运算符,而且智能指针类型不支持指针算术运算。
12.3 使用标准库:文本查询程序
课后习题:
练习12.1:在此代码的结尾,b1 和b2 各包含多少个元素?
StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
}
答:由于StrBlob的data成员是一个指向string的vector的shared_ptr,因此StrBlob的赋值不会拷贝vector的内容,而是多个StrBlob对象共享同一个(创建于动态内存空间上)vector对象。b1和b2共享的是底层数据,因此,b1和b2均包含4个string。
练习12.2:编写你自己的StrBlob 类,包含const 版本的front 和back 。
//头文件
#ifndef MY_STRBLOB_H
#define MY_STRBLOB_H
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>
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();
const string& front() const;
string& back();
const string& back() const;
private:
shared_ptr<vector<string>> data;
//如果data[i]不合法,抛出一个异常。
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);
}
}
string& StrBlob::front()
{
//如果vector为空,check会抛出一个异常
check(0, "front on empty StrBolb");
return data->front();
}
//const版本的front
const string& StrBlob::front() const
{
check(0, "front on empty StrBlob");
return data->front();
}
string& StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
//const版本的back
const string& StrBlob::back() const
{
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
return data->pop_back();
}
#endif
#include<iostream>
#include "My_StrBlob.h"
using namespace std;
int main()
{
StrBlob b1;
{
StrBlob b2 = { "a", "an", "the" };
b1 = b2;
b2.push_back("about");
cout << b2.size() << endl;
}
cout << b1.size() << endl;
cout << b1.front() << " " << b1.back() << endl;
const StrBlob b3 = b1;
cout << b3.front() << " " << b3.back() << endl;
system("pause");
return 0;
}
练习12.3:StrBlob 需要const 版本的push_back 和pop_back 吗?如果需要,添加进去。否则,解释为什么不需要。
答:push_back和pop_back的语义分别是向StrBlob对象共享的vector对象添加和从其删除元素。因此,我们不应该为其重载const版本,因为常量StrBlob对象是不应该被允许修改共享vector对象的内容的。
练习12.4:在我们的check 函数中,没有检查i 是否大于0 。为什么可以忽略这个检查?
答:check定义为私有成员函数,它只会被StrBlob的成员函数调用,而不会被用户程序调用。因此可以保证传递给它的i的值符合要求,而不必进行检查。size_type是无符号类型,一定大于等于0。
练习12.5:我们未编写接受一个initializer_list explicit (参见7.5.4 节,第264 页)参数的构造函数。讨论这个设计策略的优点和缺点。
答:未编写一个初始化列表参数的显式构造函数,意味着可以进行列表向StrBlob的隐式类型转换,在需要StrBlob的地方可以使用列表进行替代。而且还可以进行拷贝形式的初始化,这令程序编写更为简单方便。
但这种隐式转换并不总是好的。例如,列表中可能并非都是合法的值。再如,对于接受StrBlob的函数,传递给它一个列表,会创建一个临时的StrBlob对象,用列表对其初始化,然后将其传递给函数,当函数完成后,此对象将被丢弃,再也无法访问了。对于这种情况,我们可以定义显式的构造函数,禁止隐式类类型转换。
练习12.6:编写函数,返回一个动态分配的int 的vectorc 将此vector 传递给另一个函数,这个函数读取标准输入,将读入的值保存在vector 元素中。再将vector传递给另一个函数,打印读入的值。记得在恰当的时刻delete vector 。
// 练习 12.6
#include <iostream>
#include <vector>
#include <new>
using namespace std;
vector<int>* new_vector(void)
{
return new (nothrow) vector<int>;
}
void read_ints(vector<int> *pv)
{
int num;
while (cin >> num)
{
pv->push_back(num);
}
}
void print_ints(vector<int> *pv)
{
for (auto &r : *pv)
cout << r << " ";
cout << endl;
}
int main()
{
vector<int> *pv = new_vector();
if (!pv)
{
cout << "内存不足!" << endl;
return -1;
}
read_ints(pv);
print_ints(pv);
delete pv;
pv = nullptr;
system("pause");
return 0;
}
练习12.7:重做上一题,这次使用shared_ptr 而不是内置指针。
// 练习 12.7
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
shared_ptr<vector<int>> new_vector()
{
return make_shared<vector<int>>();
}
void read_ints(shared_ptr<vector<int>> spv)
{
int num;
while (cin >> num)
{
spv->push_back(num);
}
}
void print_ints(shared_ptr<vector<int>> spv)
{
for (auto &r : *spv)
cout << r << " ";
cout << endl;
}
int main()
{
shared_ptr<vector<int>> spv = new_vector();
read_ints(spv);
print_ints(spv);
system("pause");
return 0;
}
练习12.8:下面的函数是否有错误?如果有,解释错误原因。
bool b() {
int* p = new int;
//...
return p;
}
答:从程序片段看,可以猜测程序员的意图是通过new返回的指针值来区分内存分配成功或失败——成功返回一个合法指针,转换为整型是一个非零值,可转换为bool值true;分配失败,p得到nullptr,其整型值是0,可转换为bool值false.
但普通new调用在分配失败时抛出一个异常bad_alloc,而不是返回nullptr,因此程序不能达到预想目的。
可将new int改为new (nothrow) int 来令ne