C++ Primer笔记——allocator、unique_ptr和release、智能指针与动态数组、阻止拷贝

目录

一.P418 unique_ptr和release

二.P426 智能指针与动态数组

(一).unique_ptr

(二).shared_ptr

三.P428 allocator

(一).申请空间

(二).初始化构造

(三).析构

(四).释放空间

(五).相关算法

四.P450 阻止拷贝(类)


一.P418 unique_ptr和release

release函数能够返回一个普通指针指向该空间,并切断unique_ptr与该空间的联系,即放弃对该空间的管理

但是release并不会释放该空间资源,一但没有指针来接收release返回值,那么将造成资源泄漏(指针丢失)。

示例如下:

unique_ptr<int> up(new int);
up.release();//错误,资源泄漏
int* p = up.release();//正确

二.P426 智能指针与动态数组

(一).unique_ptr

unique_ptr自身提供了管理动态数组的删除器delete[],因此使用动态数组时只需要在对象类型后提供一对方括号,表明指针指向的是一个数组。

另外,当使用动态数组后,unique_ptr不再支持*与->,因为此时要将其看做数组使用。应该使用下标运算符来访问

使用方式:

unique_ptr<int[]> up(new int[10]{ 7, 7, 12 });
cout << up[2];//正确
cout << *up;//错误,不再支持解引用

(二).shared_ptr

与unique不同,shared_ptr没有提供管理动态数组的删除器。因此需要我们自定义删除器。

这里我们采用lambda表达式提供定制删除器:

shared_ptr<int> sp(new int[10], [](int* p) {delete[] p;});
//采用lambda传仿函数,使用delete[]释放数组资源

此外,shared_ptr也没有提供下标运算符,如果想要访问数组元素,使用get函数获得普通指针再访问元素

for (size_t i = 0; i < 10; i++) {
    //采用get函数获取普通指针,再按地址访问
	*(sp.get() + i) = 7;
}

三.P428 allocator

allocator定义在<memory>头文件中。

中文上叫做内存池,顾名思义,用于管理动态开辟的内存空间。

与new不同的是,它一般是提前分配了一大块未初始化的空间,使用时再根据所需空间大小进行初始化,其实就是将一块空间资源让使用者自行维护。

分配空间时步骤如下:

1.申请空间

2.初始化构造空间

(一).申请空间

首先定义一个可以申请目标类型空间的allocator类对象a。

再调用allocate函数申请n个所需目标类型对象空间,返回值是指针类型指向开辟的第一个空间。

allocator<string> a;//可以分配string空间的对象
auto p = a.allocate(128);//申请了128个未初始化的string

值得注意的是,此时的空间并不能够直接使用,因为没有进行初始化操作。 

(二).初始化构造

申请空间后,调用construct函数进行空间初始化。

allocator.construct(p, args);

p是目标空间地址的指针,args是该类型的构造函数参数。

auto q = p;//让p一直指向首元素,便于之后释放
a.construct(q++, "hello world");//字符串为"hello world"
a.construct(q++, 10, "hello");//字符串为"hhhhhhhhhh"

使用时直接按指针方式访问即可。 

 释放空间资源时,又分为两步:

1.析构

2.释放空间资源

(三).析构

调用destory函数来完成析构,其接收一个指针对象,自动调用对象的析构函数。如果有多个资源需要释放,那么要一个个调用destory函数

while (q != p) {
	a.destroy(--q);
}

(四).释放空间

最后,调用deallocate函数将已经析构的空间释放即可。

allocator.deallocate(p, n);

参数p是指针,指向释放资源起始位置,n是要释放的该类型资源的数量。

a.deallocate(p, n);//释放n个资源

(五).相关算法

算法 含义
uninitialized_copy(b, e, p); 将迭代器b到e的内容拷贝到p开始内存中
uninitialized_copy_n(b, n, p); 将迭代器b开始n个内容拷贝到p开始内存中
uninitialized_fill(b, e, t); 未初始化内存b到e的范围,初始化为t
uninitialized_fill_n(b, n, t); b所指内存开始,创建n个对象初始化为t

代码示例:

vector<int> arr = { 1, 2, 3, 4, 5, 6, 7 };
allocator<int> a;
auto p = a.allocate(128);
//例1
uninitialized_copy(arr.begin(), arr.end(), p);//将数组内容拷贝
//p:1 2 3 4 5 6 7 
//例2
uninitialized_copy_n(arr.begin(), 4, p);
//p:1 2 3 4
//例3
uninitialized_fill(p, p + 5, 1);//给空间创建对象并初始化
//p:1 1 1 1 1
//例4
uninitialized_fill_n(p, 5, 1);
//p:1 1 1 1 1

四.P450 阻止拷贝(类)

方式一:将拷贝构造和拷贝赋值函数删除

class A {
public:
	A() = default;//使用默认构造
	A(const A& a) = delete;//禁掉拷贝构造
	A& operator=(const A& a) = delete;//禁掉赋值
};

方式二:删除析构函数

这里必须指出,因为删除析构函数,栈区对象无法销毁,因此只能在堆区创建对象且无法销毁

class A {
public:
	A() = default;
	~A() = delete;
};

A a;//错误,不能析构
A* b = new A;//正确
delete b;//错误,delete不能调用析构

小编认为严格来讲,这并不是一种阻止拷贝的方式,创建一个无法销毁的对象就意味着内存泄漏的风险。

方式三:将拷贝、赋值设为私有函数

class A {
	A(const A& a);//私有拷贝和赋值
	A& operator=(const A& a);//声明但不定义
public:
	A() = default;
};

注意不要对拷贝和赋值进行定义,这样能阻止类内拷贝的发生(在链接时会报错)。

声明但不定义一个函数是合法的,但是有一个例外,就是派生类对基类虚函数的覆盖,如果只有声明将在多态调用时发生链接错误,无法找到绑定的虚函数的定义。


如有错误,敬请斧正

猜你喜欢

转载自blog.csdn.net/weixin_61857742/article/details/128453352