《C++11Primer》阅读随记 -- 十二、动态内存

第十二章 动态内存

动态内存是通过一对运算符来完成的: new,在动态内存中为对象分配空间并返回一个指向该对象的指针;delete, 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种 智能指针(smart pointer) 类型对象。智能指针的行为类似常规指针,重要的区别时它负责自动释放所指向的对象。新标准库提供的这两种智能指针的却别在于管理底层指针的方式: shared_ptr 允许多个指针指向同一个对象;unique_ptr 则“独占”所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一种弱引用,指向 shared_ptr 所管理的对象。这三种类型都定义在 memory 头文件中。

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 共享对象的智能指针数量;可能很慢,主要用于调试

make_shared 函数

// 指向一个值为 42 的 int 的 shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
// p4 指向一个值为 "9999999999" 的 string
shared_ptr<string> p4 = make_shared<string>(10, '9');
// p5 指向一个值初始化的 int, 即, 值为 0
shared_ptr<int> p5 = make_shared<int>();

通常用 auto 定义一个对象来保存 make_shared 的结果

// p6 指向一个动态分配的空 vector<string>
auto p6 = make_shared<vector<string>>();

shared_ptr 的拷贝和赋值

当进行拷贝或赋值操作时,每个 shared_ptr 都会记录有多少个其他 shared_ptr 指向相同的对象

auto p = make_shared<int>(42);	// p 指向的对象只有 p 一个引用者
auto q(p);						// p 和 q 指向相同对象,此对象有两个引用者

一旦一个 shared_ptr 计数器变为 0,它就会自动示范给自己所管理的对象

auto r = make_shared<int>(42);	// r 指向的 int 只有一个引用者;
r = q;	// 给 r 赋值,令它指向另一个地址
		// 递增 q 指向的对象的引用计数
		// 递减 r 原来指向对象的引用计数
		// r 原来指向的对象已经没有引用者,会自动释放

如果将 shared_ptr 存放于一个容器中,而后不再需要全部元素,而只是用其中一部分,要记得用 erase 删除不再需要的那些元素

使用动态生存期的资源的类
程序使用动态内存出于以下三种原因之一:

  1. 程序不知道自己需要使用多少对象
  2. 程序不知道所需对象的准确类型
  3. 程序需要在多个对象间共享数据

定义 StrBlob 类

class StrBlob{
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> il);
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	// 添加和删除元素
	void push_back(const std::string& t){ data->push_back(t); }
	void pop_back();
	// 元素访问
	std::string& front();
	std::string& back();
private:
	std::shared_ptr<std::vector<std::string>> data;
	// 如果 data[i] 不合法,抛出一个异常
	void check(size_type i, const std::string& msg) const;
};

在此类中实现了 size、empty 和 push_back 成员。这些成员通过指向底层 vectordata 成员来完成它们的工作。例如,对一个 StrBlob 对象调用 size() 会调用 data->size(),依次类推

StrBlob 构造函数
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 StrBlob");
	return data->front();
}

// const string& StrBlob::front(){} const 版本重载

string& StrBlob::back(){
	// 如果 vector 为空,check 会抛出一个异常
	check(0, "back on empty StrBlob");
	return data->back();
}

void StrBlob::pop_back(){
	check(0, "pop_back on empty StrBlob";
	data->pop_back();
}
StrBlob 的拷贝、赋值和销毁

类似 Sales_data 类,StrBlob 使用默认版本的拷贝、赋值和销毁成员函数来对此类型的对象进行这些操作。默认情况下,这些操作拷贝、赋值和销毁类的数据成员。StrBlob 类只有一个数据成员,它是 Shared_ptr 类型。因此,当我们拷贝、赋值或销毁一个 StrBlob 对象时,它的 shared_ptr 成员会被拷贝、赋值或销毁。

拷贝一个 shared_ptr 会递增其引用计数;将一个 shared_ptr 赋予另一个 shared_ptr 会递增复制好右侧 shared_ptr 的引用计数,递减左侧。如果一个 shared_ptr 的引用计数变为 0,它所指的对象会被自动销毁。因此,对于由 StrBlob 构造函数分配的 vector,当最后一个指向它的 StrBlob 对象被销毁时,它会随之被自动销毁。

使用 new 动态分配和初始化对象

string* ps1 = new string;	// 默认初始化为空 string
string* ps = new string();	// 值初始化为空 string
int* pi1 = new int;			// 默认初始化; *pi1 的值未定义
int* pi2 = new int();		// 值初始化为 0; *pi2 wei  0

指针值和 delete

int i, *pi1 = &i, *pi2 = nullptr;
double* pd = new double(33), *pd2 = pd;
delete i;	// 错误, i 不是一个指针
delete pi1;	// 未定义: pi1 指向一个局部变量
delete pid;	// 正确
delete pd2;	// 未定义, pd2 指向的内存已经被释放了
delete pi2;	// 正确: 释放一个空指针总是没有错误的

同一块内存释放两次。当有两个指针指向相同的动态分配对象时,可能发生这种错误。如果对其中一个指针进行了 delete 操作,对象的内存就被归还给自由空间了。如果我们随后又 delete 第二个指针,自由空间就可能被破坏

shared_ptr 和 new 结合使用

如果我们不初始化一个智能指针,它就会被初始化为一个空指针。我们还可以用 new 返回的指针来初始化智能指针:

shared_ptr<double> pl;	// shared_ptr 可以指向一个 double
shared_ptr<int> p2(new int(42));	// p2 指向一个值为 42 的 int

接受指针参数的智能指针构造函数是 explicit 的。因此,我们不能将一个内置指针隐式地转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针:

shared_ptr<int> p1 = new int(1024);	// 错误: 必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));	// 正确:使用了直接初始化形式
shared_ptr<int> clone(int p){
	return new int(p);	// 错误: 隐式转换为 shared_ptr<int>
}

shared_ptr<int> clone(int p){
	return shared_ptr<int>(new int(p));
}

定义和改变 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.reset(q) p.reset(q,d)p 是唯一指向其对象的 shared_ptrreset 会释放此对象。若传递了可选的参数内置指针 p, 会令 p 指向 q,否则会将 p 置为空。若还传递了参数 d,将会调用 d 而不是 delete 来释放 q

不要混合使用普通指针和智能指针

// 在函数被调用时 ptr 被创建并初始化
void process(shared_ptr<int> ptr){
	// 使用 ptr
}// ptr 离开作用域,被销毁

使用此函数的正确方法是传递给它一个 shared_ptr

shared_ptr<int> p(new int(42)); // 引用计数为 1
process(p);	// 拷贝 p 会递增它的引用计数;在 process 中引用计数为 2
int i = *p;	// 正确: 引用计数为 1

虽然不能传递给 process 一个内置指针,但可以传递给它一个( 临时的 ) shared_ptr,这个 shared_ptr 是用一个内置指针显式构造的。但是,这样做很可能会导致错误

int* x(new int(1024));	// 危险:x 是一个普通指针,不是一个智能指针
process(x);	// 错误: 不能将 int* 转换为一个 shared_ptr<int>
process(shared_ptr<int>(x));	// 合法的,但内存会被释放!
int j = *x;	// 未定义的: x 是一个空悬指针

在上面的调用中,将一个临时的 shared_ptr 传递给 process。当这个调用所在的表达式结束时,这个临时对象就被销毁了。销毁这个临时变量会递减引用计数,此时引用计数就变成 0 了。因此,当临时对象被销毁时,它所指向的内存会被释放

x 继续指向这个内存,从而变成一个空悬指针。如果试图使用 x 的值,其行为是未定义的。

当将一个 shared_ptr 绑定到一个普通指针时,我们就酱内存的管理责任交给了这个 shared_ptr 。一旦这样做了,就不应该再使用内置指针来访问 shared_ptr 所指向的内存了。

不要使用 get 初始化另一个智能指针或为智能指针赋值

智能指针类型定义了一个名为 get 的函数,它返回一个内置指针,指向智能指针管理的对象。此函数是为了这样一种情况设计的:我们需要想不能使用智能指针的代码传递一个内置指针。使用 get 返回的指针的代码不能 delete 此指针。

编译器不会给出错误信息,但将另一个智能指针也绑定到 get 返回的指针上是错误的:

shared_ptr<int> p(new int(42)); // 引用计数为 1
int* q = p.get();	// 正确: 但使用 q 时要注意,不要让他管理的指针被释放
{// 新程序块
// 未定义: 两个独立的 shared_ptr 指向相同的内存
shared_ptr<int>q(q);
}// 程序块结束,q 被销毁,它指向的内存被释放
int foo = *p; // 未定义:p 指向的内存已经被释放了

本例中, pq 指向相同的内存。由于它们时相互独立创建的,因此各自的引用计数都是 1.当 q 所在的程序块结束时,q 被销毁,这会导致 q 指向的内存被释放。从而 p 变成一个空宣子还真,意味着当我们试图使用 p 时,将会发生未定义行为。而且当 p 被销毁时,这块内存会被第二次 delete
在这里插入图片描述
其他 shared_ptr 操作

p = new int(1024);		// 错误: 不能将一个指针赋予 shared_ptr
p.reset(new int(1024));	// 正确: p 指向一个新对象

与赋值类似,reset 会更新引用计数,如果需要的话,会释放 p 指向的对象。reset 成员常常与 unique 一起使用,来控制多个 shared_ptr 共享的对象。再改变底层对象之前,我们检查自己是否时当前对象仅有的用户。如果不是,在改变之前要制作一份新的拷贝

if( !p.unique() )
	p.reset(new string(*p));	// w偶们不是唯一用户;分配新的拷贝
*p += newVal;	// 现在我们知道自己时唯一的用户,可以改变对象的值

智能指针和哑类

struct destination;					// 表示正在连接什么
struct connection;					// 使用连接所需的信息
connection connect(destination*);	// 打开连接
void disconnect(connection);		// 关闭给定的连接
void f(destination& d /* 其他参数 */){
	// 获得一个连接;记住使用后要关闭
	connection c = connect(&d);
	// 使用连接
	// 如果在 f 退出前忘记调用 disconnect, 就无法关闭 c 了
}

如果 connection 有一个析构函数, 就可以在 f 结束时由析构函数自动关闭丽娜姐。但是如果没有,就要使用我们自己的释放操作。
默认情况下,shared_ptr 假定它们指向的是动态内存。因此,当一个 shared_ptr 被销毁时,它默认地对它管理地指针进行 delete 操作。为了用 shared_ptr 来管理一个 connection,我们必须首先定义一个函数来替代 delete。这个**删除器(deleter)**函数必须能够完成对 shared_ptr 中保存地指针进行释放地操作。在本例中,我们的删除器必须接受单个类型为 connection* 的参数

void end_connection(connection* p) { disconnection(*p)}

当我们创建一个 shared_ptr 时,可以传递一个( 可选的 )指向删除器函数的参数

void f(destination& d /* 其他参数 */){
	connection c = connect(&d);
	shared_ptr<connection> p(&c, end_connection);
	// 使用连接
	// 当 f 退出(即使是由于异常退出), connection 会被正确关闭
}

p 被销毁时,他不会对自己保存的指针执行 delete, 而是调用 end_connection。接下来 end_connection 会调用 disconnect,从而确保连接被关闭。如果 f 正常退出,那么 p 的销毁会作为结束处理的一部分。如果发生了异常, p 童谣会被销毁,从而连接被关闭。

unique_ptr

shared_ptr 不同,没有类似 make_shared 的标准库函数返回一个 unique_ptr。当我们定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上。类似 shared_ptr, 初始化 unique_ptr 必须采用直接初始化形式

unique_ptr<double> p1;				// 可以指向一个 double 的 unique_ptr
unique_ptr<int> p2(new int(42));	// p2 指向一个值为 42 的 int

由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作

unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1);	// 错误: unique_ptr 不支持拷贝
unique_ptr<string> p3;
p3 = p2;					// 错误: unique_ptr 不支持赋值

unique_ptr 操作

写法作用
unique_ptr<T> u1 unique_ptr<T,D> u2unique_ptr,可以指向类型为 T 的对象。u1 会使用 delete 来释放它的指针;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.reset(q) u.reset(nullptr)释放 u 指向的对象; 如果提供了内置指针 q,令 u 指向这个对象; 否则将 u 置空

虽然不可以赋值或拷贝,但是可以通过 releasereset 将指针所有权从一个 ( 非 const) unique_ptr 转移给另一个 unique:

// 将所有权从 p1 转移给 p2
unique_ptr<string> p2(p1.release());		// release 将 p1 置为空
unique_ptr<string> p3(new string("Trex"));
// 将所有权从 p3 转移到 P2
p2.reset(p3.release()); 					// reset 释放了 p2 原来指向的内存

传递 unique_ptr 参数和返回 unique_ptr

不能拷贝 unique_ptr 的规则有一个例外: 我们可以拷贝或复制一个将要被销毁的 unique_ptr

unique_ptr<int> clone(int p){
	// 正确:从 int* 创建一个 unique_ptr<int>
	return unique_ptr<int>(new int(p));
}

还可以返回一个局部对象的拷贝

unique_ptr<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 传递删除器
unique_ptr 管理删除器的方式和 shared_ptr 不同(16章介绍)
重载一个 unique_ptr 中的删除器会影响到 unique_ptr 类型以及如何构造( 或 reset )该类型的对象。与重载关联容器的比较操作类似,我们必须在尖括号中 unique_ptr 指向类型之后提供删除器类型。在创建或者 reset 一个这种 unique_ptr 类型的对象时,必须提供一个指定类型的可调用对象( 删除器 )

// p 指向一个类型为 objT 的对象,并使用一个类型为 delT 的对象释放 objT 对象
// 它会调用一个名为 fcn 的 delT 类型对象
unique_ptr<objT, delT> p (new objT, fcn);

shared_ptr 的例子

void f(destination& d /* 其他需要的参数 */){
	connection c = connect(&d);	// 打开连接
	// 当 p 被销毁时,连接将会关闭
	unique_ptr<connection, decltype(end_connection)*>
		p(&c, end_connection);
	// 使用连接
	// 当 f 退出时( 即使时因为异常退出 ),connection 会被正确关闭
}

本例中使用了 decltype 来指明函数指针类型。由于 decltype(end_connection) 返回一个函数类型,所以我们必须添加一个 * 来指出我们正在使用该类型的一个指针

weak_ptr

weak_ptr 是一种不控制所指向对象生存期的智能指针,它指向由一个 shared_ptr 管理的对象。将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。即使有 weak_ptr 指向对象,对象还是会被释放,因此,weak_ptr 的名字抓住了这种智能指针“弱”共享对象的特点

写法作用
weak_ptr<T> w weak_ptr<T> w(sp)weak_ptr 可以指向类型为 T 的对象;与 shared_ptr sp 指向相同对象的 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

当我们创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它

auto p = make_shared<int>(42);
weak_ptr<int> wp(p);	// wp 弱共享 p; p 的引用计数未改变

由于对象可能不存在,我们不能使用 weak_ptr 直接问对象,而必须调用 lock。此函数检查 weak_ptr 指向的对象是否存在。若存在,lock 返回一个指向共享对象的 shared_ptr。与任何其他 shared_ptr 类似,只要此 shared_ptr 存在,它所指的底层对象也就会一直存在

if(shared_ptr<int> np = wp.lock()){ // 如果 np 不为空则条件成立
	// 注意这时引用计数器会 + 1
	// 在 if 中,np 与 p 共享对象
}

在这里插入图片描述

核查指针类

作为 weak_ptr 用途的一个展示,为 StrBlob 类定义一个伴随指针类。命名为 StrBlobPtr, 会保存一个 weak_ptr,指向 StrBlobdata 成员,这是初始化时提供给它的。通过使用 weak_ptr,不会影响一个给定的 StrBlob 所指向的 vector 的生存期。但是,可以阻止用户访问一个不再存在的 vector 的企图

// 对于访问一个不存在元素的尝试,StrBlobPtr 抛出一个异常
class StrBlobPtr{
public:
	StrBlobPtr():curr(0){}
	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;	// 在数组中的当前位置
};

默认构造函数生成一个空的 StrBlobPtr。其构造函数初始化列表将 curr 显示初始化为 0,并将 wptr 隐式初始化为一个空 weak_ptr

第二个构造函数接受一个 StrBlob 对象的 shared_ptr 中的 vector, 并将 curr 初始化为 sz 的值,

值得注意的时,我们不能将 StrBlobPtr 绑定到一个 const StrBlob 对象。这个限制是由于构造函数接受一个非 const StrBlob 对象的引用而导致的

StrBlobPtrcheck 成员与 StrBlob 中的同名成员不同,它还要检查指针指向的 vector 是否还存在

std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t i, const std::string& msg) const{
	auto ret = wptr.lock();	// vector 还存在嘛
	if( !ret )
		throw std::runtime_error("unbound StrBlobPtr");
	if(i >= ret->size())
		throw std::out_of_range(msg);
	return ret;
}

指针操作

deref 成员调用 check, 检查使用 vector 是否安全以及 curr 是否在合法范围内

std::string& StrBlobPtr::deref() const{
	auto p = check(curr, "dereference past end");
	return (*p)[curr]; // (*p) 是对象所指的 vector
}

如果 check 成功,p 就是一个 shared_ptr,指向 StrBlobPtr 所指向的 vector

incr 成员也调用 check

// 前缀递增:返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr(){
	// 如果 curr 已经指向容器的尾后位置,就不能递增它
	check(curr, "increment past end of StrBlobPtr");
	++cur; // 推进当前位置
	return *this;
}

当然,为了访问 data 成员,指针类必须声明为 StrBlobfriend。还要为 StrBlob 类定义 beginend 操作,返回一个指向它自身的 StrBlobPtr

// 对于 StrBlobPtr 中的友元声明来说,此前置声明式必要的
class StrBlobPtr;
class StrBlob{
	friend class StrBlobPtr;
	// ...
	// 返回指向首元素和尾后元素的 StrBlobPtr
	StrBlobPtr begin() { return StrBlobPtr(*this) };
	StrBlobPtr end(){
		auto  ret = StrBlobPtr(*this, data->size());
		return ret;
	}
};

分配一个数组会得到一个元素类型的指针

虽然通常称 new T[] 分配的内存为 “动态数组”,但实际上用 new 分配一个数组时,我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。

由于分配的内存并不是一个数组类型,因此不能对动态数组调用 beginend。这些函数使用数组维度。由于相同的原因,也不能用范围 for 语句来处理所谓的动态数组中的元素

// 初始化动态分配对象的数组
int* pia = new int[10];			// 10 个未初始化的 int
int* pia2 = new int[10]();		// 10 个值初始化为 0 的 int
string* psa = new string[10];	// 10 个空 string
string* psa = new string[]();	// 10 个空 string

int* pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
string* psa2 = new string[10]{"a", "an", "the", string(3, 'x')};
动态分配一个空数组合法
char arr[0];			// 错误: 不能定义长度为 0 的数组
char* cp = new char[0];	// 正确: 但 cp 不能解引用

当我们用 new 分配一个大小为 0 的数组时,new 返回一个合法的非空指针。此指针保证与 new 返回的其他任何指针都不相同。对于零长度的数组来说,此指针就像尾后指针一样,可以像使用尾后迭代器一样使用这个指针。

智能指针和动态数组

标准库提供了一个可以管理 new 分配的数组的 unique_ptr 版本。为了用一个 unique_ptr 管理动态数组,我们必须在对象类型后面跟一对空方括号:

// up 指向一个包含 10 个未初始化 int 的数组
unique_ptr<int[]> up(new int[10]);
up.release();		// 自动用 delete[] 销毁其指针

当一个 unique_ptr 指向一个数组时,我们不能使用点和箭头成员运算符。毕竟 unique_ptr 指向的是一个数组而不是单个对象,因此这些运算符时无意义的。另一方面,当一个 unique_ptr 指向一个数组时,我们可以用下标运算符来访问数组中的元素

for(size_t i = 0; i != 10; ++i)
	up[i] = i;	// 为每个元素赋予一个新值

指向数组的 unique_ptr 不支持成员访问运算符( 点和箭头运算符) 其他 unique_ptr 操作不变

方式作用
unique_ptr<T[]> uu 可以指向一个动态分配的数组,数组元素类型为 T
unique_ptr<T[]> u(p)u 指向内置指针 p 所指向的动态分配的数组。p 必须能转换为类型 T*
u[i]返回 u 拥有的数组中为 i 处的对象,u 必须指向一个数组

shared_ptr 需要删除器

// 为了使用 shared_ptr ,必须提供一个删除器
shared_ptr<int> sp(new int[10], [](int* p){delete[] p;});
sp.reset();	// 使用提供的 lambda 释放数组,它使用 delete[]

allocator 类

allocator 是一个模板。

allocator<string> alloc;			// 可以分配 string 的 allocator 对象
auto const p = alloc.allocate(n);	// 分配 n 个未初始化的 string
算法作用
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* 的指针,指向一块原始内存;arg 被传递给类型为 T 的构造函数,用来在 p 指向的内存中构造一个对象
a.destroy(p)pT* 类型的指针,此算法对 p 指向的对象执行析构函数
// auto q = p;	// q 指向最后构造的元素之后的位置
alloc.construct(q++);			// *q 为空字符串
alloc.construct(q++, 10, 'c');	// *q 为 cccccccccc
alloc.construct(q++, "hi");		// *q 为 hi
cout << *q << endl;		// 正确:使用 string 的输出运算符
cout << *p << endl;		// 灾难:q 指向为构造的内存

在这里插入图片描述

在这里插入图片描述
当使用完对象后,必须对每个构造的元素调用 destroy 来销毁。destroy 接受一个指针,对指向的对象执行析构函数

while(q != p)
	alloc.destroy(--q);

// 不再使用就将内存归还给操作系统
alloc.deallocate(p, n);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Artintel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值