目录
一.P418 unique_ptr和release
二.P426 智能指针与动态数组
(一).unique_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;
};
注意不要对拷贝和赋值进行定义,这样能阻止类内拷贝的发生(在链接时会报错)。
声明但不定义一个函数是合法的,但是有一个例外,就是派生类对基类虚函数的覆盖,如果只有声明将在多态调用时发生链接错误,无法找到绑定的虚函数的定义。
如有错误,敬请斧正