C++ Primer学习笔记 - 第12章 动态内存与智能指针
12.1 动态内存与智能指针
new, delete运算符:动态申请,释放内存;
智能指针:shared_ptr,unique_ptr,weak_ptr,由标准库提供,头文件 memory
12.1.1 shared_ptr类
允许多个指针指向同一个对象。默认初始化的shared_ptr指针为空指针,使用方式同普通指针,解引用返回所指向的对象。
// shared_ptr 定义
shared_ptr<string> p1; // p1 指向string
shared_ptr<list<int>> p2; // p2 指向list<int>
// 智能指针的使用,解引用以及检测是否为空
if (p1 && p1->empty()) {// 如果指针p1非空,并且指向一个空string
*p = "hello"; // 将一个新值赋予string
}
// 默认初始化的shared_ptr指针为空指针,验证方式
shared_ptr<string> pps;
if (!pps) cout << "shared_ptr is null" << endl; // 打印 "shared_ptr is null"
else cout << "shared_ptr is not null" << endl;
shared_ptr重要操作
swap(p,q); // 交换p和q的指针
p.swap(q);
make_shared<T>(args); // 返回一个shared_ptr指针,指向一个动态分配的类型为T的对象。args用于初始化改对象
shared_ptr<T> p(q); // p拷贝构造q,q中计数器+1。要求q是shared_ptr,且q的指针能转化成T*
p = q; // p,q都是shared_ptr指针。p计数-1,q计数+1.p计数为0时,p指向内存释放
p.unique(); // 若p.use_count() == 1,返回true,否则返回false
p.use_count(); // 与p共享对象的智能指针计数。可能速度慢,主要用于调试
make_shared()函数
标准库函数make_shared,是安全分配和使用动态内存的方法。
// make_shared使用方法
// p3指向值为42的int
shared_ptr<int> p3 = make_shared<int>(42);
// p4指向值为"9999999999" (10个'9')
shared_ptr<string> p4 = make_shared<string>(10, '9');
// p5指向值为0的int
shared_ptr<int> p5 = make_shared<int>();
shared_ptr拷贝和赋值
拷贝shared_ptr时,与其关联的引用计数(reference count)会递增。shared_ptr赋予新值,或离开作用域被销毁时,计数器递减。
一旦shared_ptr计数值为0,它会自动释放自己所管理的对象。
auto r = make_shared<int>(42); // r指向的int只有一个引用者
r = q; // r赋值,指向另外一个地址,引用计数-1。q引用计数+1。r原来指向的对象没有引用者,自动释放
shared_ptr自动销毁、释放关联内存
shared_ptr销毁对象具体工作,通过析构函数(destructor)来完成。
shared_ptr在离开作用域时,会自动释放关联内存,依据是引用计数是否为0。
shared_ptr<Foo> factory(T arg) {
return make_shared<Foo>(arg);
}
// 版本1:p离开作用域,引用计数-1 == 0,自动释放p所指向内存
void use_factory(T arg) {
shared_ptr<Foo> p = factory(arg);
}
// 版本2:p离开作用域,但返回p时,会拷贝到一个临时缓存,引用计数-1+1 != 0,p所指向内存不会释放
shared_ptr<Foo> use_factory(T arg) {
shared_ptr<Foo> p = factory(arg);
return p;
}
使用了动态生存期的资源的类
程序使用动态内存通常原因:
- 不知道自己需要使用多少对象;
- 不知道所需对象的准确类型,如在使用基类指针的时候,不知道具体使用的哪个派生类,还是基类自身;
- 需要多个对象间共享数据;
12.1.2 直接管理内存
使用2个运算符:new,delete来分配、释放动态内存。
使用new动态分配和初始化对象
// new基本类型
int *p1 = new int; // p1指向未初始化无名对象(随机值)
int *p2 = new int(); // p1指向初值为0的int
int *p3 = new int(100); // p1 指向初值为100的int
// new容器类型
string *ps = new string; // ps指向空string
string *ps2 = new string(10, '9'); // 指向"9999999999"
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9}; // pv指向vector<int>,元素序列为花括号内容
// new自动推断类型
auto p1 = new auto(obj); // p1指向与obj同类型对象
auto p2 = new auto{a,b,c}; // 错误:括号中只能有单个初始化器
// new const对象
const int* pci = new const int(100); // p1指向const int,值为100
const string* pcs = new const string; // p1指向const string,值为空
// 内存耗尽,抛出bad_alloc异常。bad__alloc和nothrow头文件new
// 分配失败,new 返回空指针
int *p1 = new int; // 如果分配失败,抛出std::bad_alloc异常
// 定位new,允许向new传递额外参数
int *p2 = new (nothrow) int; // 如果分配失败,返回空指针。(nothrow)对象强制不抛出异常
delete释放动态内存
delete用于释放new出来的动态内存。传递给delete的指针必须指向动态分配的内存,或者空指针。
如果释放一块非new分配的内存,或者将相同的指针值释放多次,行为未定义。
delete p; // p必须指向一个动态分配的对象或是一个空指针
// delete和指针使用
int i = 0, *pi1 = &i, *pi2 = nullptr;
double *pd = new double(33);
delete i; // 错误:因为i不是指针
delete pi1; // 错误:pi1指向局部变量i
delete pi2; // 正确
delete pi2; // 正确:多次释放空指针没有问题
delete pd; // 正确
delete pd; // 错误:pd所指向内存已释放
// delete const对象
const int *pci = new const int(100);
delete pci; // 正确:不能修改const值,但能释放const对象
动态内存关联存在的问题
- 忘记delete内存。很难排查。
- 使用已经释放的对象。通过在释放内存后,将指针直为空。
- 同一块内存释放两次。
建议:尽量使用智能指针(shared_ptr等),可以有效避免new、delete管理动态内存带来的这3个问题。
如果要使用new、delete,delete释放动态内存后指针称为空悬指针(dangling pointer),建议将空悬指针置为nullptr。
delete后重置指针的方法,只对该指针有效,对其他扔指向已释放的内存的指针无效。
int *p(new int(42));
auto q = p;
delete p; // 释放了p所指内存,p和q都无效
p = nullptr; // 重置p,对q没有影响
12.1.3 shared_ptr和new结合使用
智能指针默认初始化为空。可以让智能指针指向new出来的指针。
接受指针参数的构造函数是explicit的,也就是说,不能将一个内置指针隐式转化成智能指针,必须使用直接初始化形式来初始化智能指针,而不能用拷贝初始化。
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放关联对象。如果智能指针绑定到其他类型指针上,必须提供自己的操作来替代delete。
shared_ptr<double> p1; // p1 为空指针
shared_ptr<int> p2(new int(42)); // p2指向一个值为42的int
shared_ptr<int> p1 = new int(100); // 错误:智能指针必须使用直接初始化
shared_ptr<int> p2 (new int(100)); // 正确
shared_ptr<int> clone(int p) {
return new int(p); // 错误:隐式转化成shared_ptr<int>
}
shared_ptr<int> clone(int p) {
return shared_ptr(new int(p)); // 正确:显示转化成shared_ptr<int>
}
定义和改变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
// 若p是唯一指向其对象的shared_ptr,reset会释放此对象。如果传递了指针q,则p指向q,否则p置为空。如果传递了参数d,将会调用d而非delete。
p.reset();
p.reset(q);
p.reset(q, d);
不要混合使用普通指针和智能指针...
推荐使用make_shared,不推荐使用new,因为shared_ptr可以协调对象的析构,但仅限于自身的拷贝(shared_ptr之间)。这样能在分配对象的同时将shared_ptr与之绑定,从而避免无意中将同一块内存绑定到多个独立创建的shared_ptr上。
如果将普通指针转换成智能指针,作为参数传递给函数后,可能造成普通指针指向的内存通过智能指针释放,而转变成空悬指针。
void process(shared_ptr<int> ptr) {
// use ptr
}// 离开作用域,ptr计数-1,ptr及指向内存被销毁
// 正确使用方法:不要混合使用普通指针和智能指针,注意并不是不能混合使用new和shared_ptr
shared_ptr<int> p(new int(42)); // 引用计数为1
process(p); // process中p引用计数为2
int i = *p; // 正确:引用计数为1
// 错误使用方法:智能指针引用计数为0 会导致普通指针变成空悬指针
int* x(new int(42);
process(x); // 错误:不能将int*转换成shared_ptr<int>,也就是实参类型与形参类型不匹配
process(shared_ptr<int>(x)); // 合法,但内存会被释放
int j = *x; // 未定义行为:因为x已经成为空悬指针
...也不要使用get初始化另一个智能指针或为智能指针赋值
因为通过get,让另外智能指针指向原来的内存区域,当该智能指针(独立的)引用计数为0时,会释放指向的内存,从而导致原来的智能指针称为空悬指针。而对空悬指针的解引用访问,会造成未定义行为。
shared_ptr<int> p(new int(42)); // 引用计数为1
int *q = p.get(); // 正确:但用q的时候,不要让它管理的指针被释放
{// 新程序块
// 未定义:两个独立的shared_ptr指向相同内存
shared_ptr<int> (q);
}// 程序块结束,q被销毁,它指向的内存被释放
int foo = *p; // 未定义:p指向的内存已经被释放了
其他shared_ptr的操作
用reset将一个新指针赋予一个shared_ptr,reset与赋值类似,会更新引用计数。reset经常搭配unique一起使用
shared_ptr<int> p;
p = new int(1024); // 错误;类型不兼容,不能将一个指针赋予shared_ptr
p.reset(new int(1024); // 正确:p指向一个新对象
// reset搭配unique使用
unique_ptr<string> p("hello");
if (!p.unique()) p.reset(new string(*p)); // p不是唯一用户,分配新拷贝
*p += newVal; // reset之后,p是唯一的用户,可以改变对象的值
12.1.4 智能指针和异常
智能指针管理的内存,即使程序由于发生异常过早结束,智能指针类也能确保内存不需要时释放。而new出来的普通指针,如果在手动delete之前,是不会自动释放内存的。
// 函数结束时,shared_ptr自动释放内存
void f() {
shared_ptr<int> sp(new int(42)); // 分配一个新对象
// 发生异常,f中未捕获
}
// 函数结束,delete不能正常是否内存
void f() {
int *ip = new int(42); // 动态分配一个新对象
//发生异常,f中未捕获
delete ip; // 无法正常执行delete释放内存
}
智能指针和哑类
可以用智能指针,来释放没有定义析构函数、又分配了资源的类。因为程序员很可能会忘记释放资源。
// 使用connection类进行网络编程,经常忘记disconnect释放资源
struct destination;
struct connection; // connection没有析构函数,需要用disconnect释放资源
connection connect(destination*); // 打开连接
void disconnect(connection); // 关闭指定连接
// 错误范例:忘记disconnect,导致资源没能正常释放
void f(desitnation &d) {
// 获得一个连接:记住使用完后要关闭它
connection c = connect(&d);
// 使用连接connection对象
// 如果f退出前忘记调用disconnect,就无法关闭c
}
// 通过shared_ptr & 释放操作来实现自动释放资源
void f(destination &d) {
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection); // end_connection调用disconnect,这样就能确保连接被关闭了
// 使用连接
// 当f退出时(即使由于异常退出),connection会被正确关闭。这里end_connection是p的删除器
}
智能指针基本规范:
- 不使用相同的内置指针值初始化/reset多个智能指针; -- 可以用一个智能指针初始化另外一个智能指针
- 不delete get()返回的指针; -- get返回的指针是普通指针,混用普通指针和智能指针可能导致未定义行为
- 不使用get()初始化或reset另一个智能指针;-- 同上一点,get返回的是普通指针,同时get原来的指针是一个智能指针,这样会让两个独立的智能指针指向同一块内存。reset也是一样的道理
- 如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,指针就变成无效了; -- get返回的普通指针和智能指针指向同一块内存
- 如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器;
12.1.5 unique_ptr
一个unique_ptr“拥有”它所指向的对象。
与shared_ptr不同:
- 某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁;
- 没有类似make_shared的标准库函数返回一个unique_ptr,定义一个unique_ptr时,需要将其绑定到一个new返回的指针上;
// unique_ptr支持直接初始化
unique_ptr<double> p1; // p1是可以指向double的unique_ptr
unique_ptr<int> p2(new int(42)); // p2指向值为42的int
// unique_ptr不支持普通拷贝或复制
unique_ptr<string> p1(new string("Stegosarus")); // 正确
unique_ptr<string> p2(p1); // 错误:unique_ptr不支持拷贝
unique_ptr<string> p3; // 正确
p3 = p2; // 错误:unique_ptr不支持赋值
unique_ptr操作*
// 空unique_ptr,可以指向T类型对象。u1使用delete来释放指针;u2使用类型为D的可调用对象来释放指针
unique_ptr<T> u1;
unique_ptr<T, D> u2;
unique_ptr<T, D> u(d); // 使用类型为D的对象d代替delete
u = nullptr; // 释放u所指向的对象,将u置空
u.release(); // u放弃对指针的控制权,返回指针,并将u置为空
u.reset(); // 释放u所指的对象
u.reset(q); // 如果提供了内置指针q,令u指向这个对象;否则将u置空
u.reset(nullptr);
既然不能复制或拷贝unique_ptr,那么如何将指针的所有权从一个unique_ptr转移给另一个unique了?
可以通过release或reset。
// 将所有权从p1(指向string Stegosaurus)转移给p2
unique_ptr<string> p2(p1.release()); // release将p1置为空
unique_ptr<string> p3(new string("Trex"));
// 将所有权从p3转移给p2
p2.reset(p3.release()); // reset释放了p2原来指向的内存
// 错误:没有保存release返回的指针,程序就要负责资源的释放
p2.release();
// 正确:但必须记得delete(p)
auto p = p2.release();
传递unique_ptr参数和返回unique_ptr*
不能拷贝unique_ptr规则有一个例外:可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的是从函数返回一个unique_ptr。
下面这两段代码,编译器都知道要返回的对象将要被销毁,此时,编译器执行一种特殊的“拷贝”。
unique<int> clone(int p) {
return unique<int>(new int(p));
}
// 还可返回一个局部对象的拷贝
unique<int> clone(int p) {
unique_ptr<int> ret(new int(p));
// ...
return ret;
}
向后兼容:auto_ptr
标准库的早起版本包含一个名为auto_ptr的类,具有unique_ptr部分特性,但不是全部。不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。虽然auto_ptr是标准库的一部分,但编写程序时应该使用unique_ptr。
向unique_ptr传递删除器*
shared_ptr, unique_ptr默认用delete释放所指向的对象。类似地,可以重载一个unique_ptr中默认的删除器,不过必须值尖括号中unique_ptr指向类型之后提供删除器类型。在创建或reset一个unique_ptr类型的对象时,必须提供一个指定类型的可调用对象(删除器):
unique_ptr<objT, delT> p(new objT, fcn); // p 指向类型为objT的对象,并使用一个类型为delT的对象释放objT对象,p销毁时会调用名为fcn的delT类型对象
// 用unique_ptr代替shared_ptr
void f(destination &d) {
connection c = connect(&d);
unique_ptr<connection, decltype(end_connection) *> p(&c, end_connection); // p销毁时,连接将会关闭
// 使用连接
// f退出时(不论是否由于异常而退出),connection会被正确关闭
}
12.1.6 weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,指向一个由shared_ptr管理的对象。weak_ptr绑定到shared_ptr不会改变引用计数。
如果shared_ptr引用计数为0,即使有weak_ptr指向对象,对象还是会释放。
// weak_ptr操作
weak_ptr<T> w; // 空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp); // weak_ptr w与shared_ptr sp指向相同对象,T必须能转化为sp指向的类型
w = p; // p可以是一个shared_ptr或waek_ptr。赋值后,w与p共享对象
w.reset(); // 将w置空
w.use_count(); // 与w共享对象的shared_ptr的数量
w.expired(); // 若w.use_count()为0,返回true,否则返回false
w.lock(); // 如果w.expired()为true,返回空shared_ptr;否则返回一个指向w的对象的shared_ptr
创建weak_ptr时,需要用shared_ptr来初始化
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp 弱共享p;p的引用计数未改变
由于weak_ptr指向的对象可能不存在,需要调用lock,检查weak_ptr指向的对象是否存在。
if (shared_ptr<int> np = wp.lock()) {// 如果np不为空则条件成立
// np 与wp共享,指向同一个对象
}
核查指针类*
示例,将StrBlobPtr定义为一个伴随指针类,保存weak_ptr指向成员data。使用weak_ptr不会影响给定StrBlob所指向的vector生命周期,但是可以阻止用户访问不再存在的vector的企图。
StrBlobPtr有2个数据成员:wptr,要么为空,要么指向一个StrBlob中的vector: curr,保存当前对象所表示的元素的下标。我们的指针类也有一个check成员来检查解引用StrBlobPtr是否安全。
// 尝试范围不存在的元素,StrBlobPtr抛出一个异常
class StrBlobPtr {
public:
StrBlobPtr(): curr(0) {} // 默认构造函数,将curr显示初始化为0,并将wptr隐式初始化为空weak_ptr
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) { }
std::string& deref() const;
StrBlobPtr& incr(); // 前缀递增
private:
// 若检查成功,check返回一个指向vector的shared_ptr
std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;
// 保存weak_ptr,意味着底层vector可能会被销毁
std::weak_ptr<std::vector<std::string>> wptr;
std::size_t curr; // 在数组中的当前位置
};
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t, const std::string&) cosnt {
auto ret = wptr.lock(); // wptr指向的vector内存不一定存在了,需要检查
if (!ret)
throw std::runtime_error("unbound StrBlobPtr");
if (i >= ret->size())
throw std::out_of_range(msg);
return ret; // 指向vector的shared_ptr
}
// 解引用weak_ptr,这里的例子是先用check获取weak_ptr wptr对应的shared_ptr,再解引用的
std::string&
StrBlobPtr::deref() const {
auto p = check(curr, "dereference past end");
return (*p)[curr]; // p是指向vector的shared_ptr,*p代表对象vector,(*p)[curr]代表vector中下标为curr的元素
}
// 返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr() {
// 如果curr已经位于容器末尾,则不能递增
check(curr, "increment past end of StrBlobPtr");
++curr; // 递增当前位置
return *this;
}
// StrBlob类
class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr; // 为了让StrBlobPtr访问StrBlob的私有成员data
// 为StrBlob定义begin和end操作,返回一个指向它自身的StrBlobPtr
StrBlobPtr begin() { return StrBlobPtr (*this); }
StrBlobPtr end() {
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
}
12.2 动态数组
vector, string等对象都在连续内存中保存元素,容器需要重新分配内存时,必须一次性为很多元素分配。
为此,C++和标准库定义了另一种new表达式语法,可以分配并初始化一个对象数组:名为allocator的类,允许分配和初始化分离。
使用allocator通常会提供更好的性能和更灵活的内存管理能力。
使用容器类和分配动态数组的类
建议:大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更简单,更不容易出现内存关联错误,并且可能有更好的性能。
使用容器的类,可以使用默认版本的拷贝、赋值和析构操作。
分配动态数组的类,必须定义自己的操作,中拷贝、复制以及销毁对象时管理所关联的内存。
12.2.1 new和数组
new T[]分配得到的“动态数组”并不是一个数组类型,而是一个元素类型指针。
// new分配对象数组,方括号必须指定要分配对象的数目,可以不是常量,但必须是整型
int *pia = new int[get_size()]; // pia 指向第一个int
// 用typedef表示数组类型别名
typedef int arrT[42]; // arrT表示42个int的数组类型
int *p = new arrT; // <=> int *p = new int[42];
初始化动态分配对象的数组
如果初始化器指定的元素个数 > 分配的数组大小,则编译会报错:初始化无法从initialist_list转换成int[10];
如果初始化器指定的元素个数 <= 分配的数组大小,则开始部分元素用初始化器初始化,后面的元素按值初始化。
int *pia = new int[10]; // 10个未初始化int
int *pia2 = new int[10](); // 10个值初始化为0的int
int *psa = new string[10]; // 10个空串
int *psa2 = new string[10](); // 10个空串
// 花括号列表初始化
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9}; // 10个int用列表中的初始化器初始化
string *psa3 = new string[10]{"a", "an", "the", string(3, 'x')}; // 前4个用给定初始化器初始化,剩余的进行值初始化
动态分配一个空数组是合法的*
静态空数组非法,动态空数组是合法的。
char arr[0]; // 错误:不能定义长度为0的数组,数组长度至少为1
char* cp = new char[0]; // 正确:但不能解引用cp,即使解引用也不会报错,会警告使用未初始化的内存"cp"
释放动态数组
delete释放动态数组
delete p; // p必须指向一个动态分配的对象或为空
delete [] pa; // pa必须指向一个动态分配的数组或为空
// 释放一个类型别名定义的数组类型,也必须按释放数组形式释放
typedef int arrT[42];
int* p = new arrT;
delete [] p;
智能指针和动态数组
标准库提供了unique_ptr,用于管理new分配的数组,对象类型后有一对空方括号。
unique_ptr<int[]> up(new int[10]); // 注意unique_ptr对象类型的[]
up.reset(); // 自动用delete[] 销毁其指针
// 指向数组的unique_ptr不支持成员访问运算符(.和 ->)
// 访问unique_ptr指向的动态数组
for (size_t i = 0; i != 10; ++i)
up[i] = i; // 为每个元素赋值
shared_ptr不直接支持管理动态数组。如果希望用shared_ptr管理一个动态数组,必须提供自定义的删除器。
// 为使用shared_ptr管理动态数组,必须提供删除器
shared_ptr<int> sp(new int[10], [](int* p){ delete [] p;}); // 绑定动态数组和shared_ptr,注意对象类型。删除器是lambda表达式
sp.reset(); // 使用提供的lambda释放数组
// 利用shared_ptr访问数组,不支持下标运算符,不支持指针的算术运算
for (size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i; // 使用get获得内置指针
12.2.2 alllocator类
new局限性:new分配内存时,和对象构造组合到了一起。类似的,delete将对象析构和内存释放组合在一起。也就是new对象时,会调用构造函数进行构造,而有时我们并不希望分配内存就立即进行构造。
allocator能将内存分配和对象构造分离,只有真正需要时才真正执行对象创建。它分配的内存是原始的、未构造的。
头文件<memory>
allocator类的使用示例
// 直接使用new/delete将内存分配和构造绑定到一起,可能导致不必要的浪费
string* const p = new string[n]; // 构建n个string
string s;
string* q = p; // q指向第一个string
while (cin >> s && q != p + n) {
*q++ = s; // 赋予*q新值(拷贝),并移动到下一个位置
}
const size_t size = q - p;
// 使用数组p
delete[] p; // 释放动态数组p
// allocator将内存分配和构造分离
allocator<string> alloc; // 定义allocator,能分配string
auto p = alloc.allocate(n); // 分配n个未初始化的string。n不能省略。
auto q = p;
alloc.construct(p++, "hello"); // 在p指向的内存中构造一个string对象为"hello", *q = "hello"
alloc.construct(p++, ", this is mike"); // *q = ", this is mike"
alloc.construct(p++, 3, '!'); // *q = "!!!"
cout << *q << endl; // 正确:q为初始化的初始区域
cout << *p << endl; // 错误:此时p已经指向未初始化区域
// 销毁对象
while (p != q)
alloc.destroy(--p); // 销毁对应.construct构造的string对象
// 释放内存
alloc.deallocate(p, n); // 释放对应.allocate分配的内存,这里n必须与申请的内存大小n一样,表示要释放的元素个数
拷贝和填充未初始化内存的算法
allocator的两个伴随算法,可在未初始化内存中创建对象。
// allocator算法:这些函数中给定目的位置创建元素,不是由系统分配内存
uninitialized_copy(b, e, b2); // 从迭代器范围[b, e)拷贝元素到b2为起始迭代器的原始内存。要求b2指向的内存必须足够大,能存储输入元素的拷贝
uninitialized_copy_n(b, n, b2); // 从迭代器b开始,拷贝n个元素到b2
unintialized_fill(b, e, t); // 迭代器范围[b, e)用值t填充
unintialized_fill_n(b, n, t); // 从b开始n个元素,用值t填充
// 示例:一个vector<int>容器,将其内容拷贝到动态内存,占用动态内存一半空间,另外一半用固定值填充
vector<int> vec = {...}; // 初始化vector<int>
allocator<int> alloc; // 定义allcator
// 分配vector 2倍空间
auto p = alloc.allocate(vec.size() * 2);
auto q = unitialized_copy(vec.begin(), vec.end(), p); // 用迭代器范围值,填充未初始化内存的前一半
uninitialized_fill_n(q, vec.size(), 42); // 用固定值42填充剩下一半未初始化内存
// 释放内存
alloc.deallocate(q, vec.size() *2);