目录
一、动态分配
1.初始化的三种方式
2. 释放内存
(1)悬空指针
3.创建新工程与观察内存泄漏
二、深入了解new/delete关键字
1.new和delete
2.operator new()和operator delete()
3.申请和释放一个数组
三、智能指针
(1)常规初始化
(3)引用计数
(3.1)引用计数增加
(3.2)引用计数减少
(4.1)use_count成员函数
(4.2)unique成员函数
(4.3)reset成员函数
(4.4)get成员函数
(4.5)swap成员函数
(5)自定义删除器
(6)使用建议、陷进与禁忌
(6.1)慎用裸指针
(6.2)慎用get返回的指针
(6.4)避免互相循环引用
(7)移动语义
2.weak_ptr
(1)weak_ptr常用操作
(1.1)use_count成员函数
(1.2)expired成员函数
(1.3)reset成员函数
(1.4)lock成员函数
(2)尺寸问题
(3)控制块
3.unique_ptr
(1)常规初始化
(2)make_unique
(3)常用操作
(3.1)移动语义
(3.2)release成员函数
(3.3)reset成员函数
(3.4)get成员函数
(4)尺寸问题
(5)删除器
四、智能指针的选择
一、动态分配
由程序员自己用new来为对象分配内存(new返回的是该对象的指针)的方式叫作动态分配。这种动态分配内存的方式就相当于对内存直接进行管理,具体地说就是针对new和delete的应用。
int *p = new int; //*p为一个随机值
这个分配的int对象其实没有名,或者说是一个无名对象,但是new可以返回一个指向该对象的指针,可以通过这个指针操作这个无名的int对象。
1.初始化的三种方式
很多内置类型(如int类型)对象,如若不主动提供初值,其初值就是未定义的(不确定的),如上方所示代码。
还有一种初始化的说法叫值初始化。自己定义的类,在new该类的对象时,所谓的值初始化是没有意义的。所以有意义的是类似int的这种内置类型。看一看值初始化的写法:
int *p1 = new int();//值初始化,*p为0
当然,也可以new一个对象的同时进行初始化:
int *p2 = new int(100);//new一个对象的同时进行初始化,*p为100
2. 释放内存
delete一块内存,只能delete一次,不可以delete多次。当然,可以多次delete一个空指针,但并没有什么实际意义。
提倡在delete一个指针后会把该指针设置为空(p=nullptr;)。因为一个指针即便被delete,该指针中依然保存着它所指向的那块动态内存的地址,此时该指针叫悬空指针(也叫野指针,程序员不能再操作这个指针所指向的内存),那么如果给该指针一个nullptr,表示该指针不指向任何内存,是一个好的习惯。
const对象值不能被改变,但可以被delete:
const int *p = new int(100);
delete p;
p = nullptr;
(1)悬空指针
悬空指针(也被称为野指针)是指向已经被释放或者删除的内存块的指针。在C++中,这通常发生在以下几种情况:
1. 当一个对象被删除,但还有一个或多个指针仍然引用它。
2. 当函数返回后,局部变量的地址仍然被保留。
3. 当动态分配的内存已经被释放,但是仍有指针引用。使用悬空指针可能会导致程序崩溃、数据损坏和不可预测的行为。要避免这种情况,应该确保在删除对象或释放内存后将所有相关的指针设置为nullptr,并且不要返回局部变量的地址。
int* ptr = new int(5); // 动态分配内存
delete ptr; // 释放内存
ptr = nullptr; // 将ptr设置为nullptr以避免成为悬空指针
请注意,在C++11及更高版本中,在delete之后将智能指针设为空可以自动防止悬空引用。
std::unique_ptr<int> ptr(new int(5)); // 使用智能指针
ptr.reset(); // 释放内存并将ptr设置为空
3.创建新工程与观察内存泄漏
可以用“MFC应用”来观察内存泄漏。“MFC应用”的特点是:在程序运行结束(退出)的时候,如果这个程序有内存泄漏,“MFC应用”能够报告出来。
“Detected memory leaks!”字样,翻译成中文表示出现了内存泄漏。而且泄漏的地方有2处,1处是8字节,1处是28字节,总共泄漏36字节。
二、深入了解new/delete关键字
1.new和delete
new和delete都是关键字(也叫运算符/操作符),都不是函数。malloc和free用于C语言编程中,而new和delete用于C++编程中。
new/delete和malloc/free最明显的区别之一就是使用new生成一个类对象时系统会调用该类的构造函数,使用delete删除一个类对象时系统会调用该类的析构函数(释放函数)。既然有调用构造函数和析构函数的能力,这就意味着new和delete具备针对堆所分配的内存进行初始化(把初始化代码放在类的构造函数中)和释放(把释放相关的代码放在类的析构函数中)的能力,而这些能力是malloc和free所不具备的。
2.operator new()和operator delete()
operator new(…)与operator delete(…)实际上是函数。那么,这两个函数和new/delete操作符有什么关系呢?
(1)new运算符做了两件事:①分配内存;②调用构造函数初始化该内存。new运算符是怎样分配内存的呢?new运算符就是通过调用operator new(…)来分配内存的。这个函数能直接调用,但一般很少有人这样做。
(2)delete运算符也做了两件事:①调用析构函数;②释放内存。delete运算符就是通过调用operator delete()来释放内存的。
3.申请和释放一个数组
为数组动态分配内存时,往往需要用到[],如new[…],而释放数组时,往往也要用到[],如delete[…],这意味着,往往new[]和delete[]要配套使用。
class My_Class {
public:
My_Class()
{
std::cout << "构造函数"<<std::endl;
}
~My_Class()
{
std::cout << "析构函数"<<std::endl;
}
};
int main()
{
My_Class *myClass = new My_Class[2]; //调用两次构造函数
delete[] myClass; //调用两次析构函数
return 0;
}
为什么这个调用会导致调用两次类的析构函数?系统如何知道new的时候new出了几个数组元素(类对象)呢?
C++的做法是在分配数组空间时多分配了4字节的大小,专门保存数组的大小,在delete []时就可以取出这个数组大小的数字,就知道了需要调用析构函数多少次了。
内置类型如int类型,delete的时候不存在调用类的析构函数的说法(只有类对象或类对象数组delete的时候才存在调用类的析构函数的说法),所以new的时候系统并没有多分配4字节内存。
- 如果把类的析构函数注释掉,则用new[]为对象数组分配内存,而用单独的delete来释放内存,不会异常。
class My_Class {
public:
My_Class()
{
std::cout << "构造函数"<<std::endl;
}
//~My_Class()
//{
// std::cout << "析构函数"<<std::endl;
//}
};
int main()
{
My_Class *myClass = new My_Class[2]; //调用两次构造函数
delete myClass; //没问题
return 0;
}
- 如果类书写了自己的析构函数,则用new[]为对象数组分配内存,而用单独的delete来释放内存,就会报异常。
class My_Class {
public:
My_Class()
{
std::cout << "构造函数"<<std::endl;
}
~My_Class()
{
std::cout << "析构函数"<<std::endl;
}
};
int main()
{
My_Class *myClass = new My_Class[2]; //调用两次构造函数
delete myClass; //会出异常
return 0;
}
报异常的原因是,代码行“delete myClass;”做了两件事:
(1)调用一次类的析构函数。new的时候创建的是两个对象,调用的是两次构造函数,而释放的时候调用的是一次析构函数,虽然不致命,但也存在后遗症(如类的构造函数中如果分配了内存,指望在析构函数中释放内存,那么如果少执行一次析构函数,就会直接导致内存的泄漏)。
(2)调用“operator delete(myClass);”来释放内存。系统所报的异常,其实就是执行这行代码的调用导致的。就是因为多分配4字节的问题导致释放的内存空间错乱。
所以,如果一个对象,是用new[]分配内存,而却用delete(而不是delete[])来释放内存,那么这个对象满足的条件是:对象的类型是内置类型(如int类型)或者是无自定义析构函数的类类型。
三、智能指针
直接new一个对象的方式返回的是一个对象的指针,这个对象的指针很多人称其为裸指针。所谓裸,指的就是这种直接new出来的指针没有经过任何包装的意思。显然,这种指针功能强大,使用灵活,同时开发者也必须全程负责维护,一不小心就容易犯错,一旦使用错误,造成的问题可能就极大。
C++标准库中有4种智能指针,即std::auto_ptr、std::unique_ptr、std::shared_ptr、std::weak_ptr。每一个都有适用的场合,它们的存在就是为了帮助程序员管理动态分配的对象(new出来的对象)的生命周期,既然智能指针能够管理对象的生命周期,所以能够有效地防止内存的泄漏。
目前std::auto_ptr已经完全被std::unique_ptr所取代,所以不要再使用std::auto_ptr。C++11标准中也反对再使用(弃用)std::auto_ptr了。
auto_ptr有些使用上的限制(缺陷),如不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。
- shared_ptr是共享式指针的概念。多个指针指向同一个对象,最后一个指针被销毁时,这个对象就会被释放。
- weak_ptr这个智能指针是用来辅助shared_ptr工作的。
- unique_ptr是一种独占式指针的概念,同一个时间内只有一个指针能够指向该对象,当然,该对象的拥有权(所有权)是可以移交出去的。
1.shared_ptr
shared_ptr指针采用的是共享所有权来管理所指向对象的生存期。所以,对象不仅仅能被一个特定的shared_ptr所拥有,而是能够被多个shared_ptr所拥有。多个shared_ptr指针之间相互协作,从而确保不再需要所指对象时把该对象释放掉。
在决定是否采用这种智能指针时首先得想一个问题:所指向的对象是否需要被共享,如果需要被共享(类似于多个指针都需要指向同一块内存),那就使用shared_ptr,如果只需要独享(只有一个指针指向这块内存),就建议使用unique_ptr智能指针,因为shared_ptr虽然额外开销不大,但毕竟为了共享还是会有一些额外开销。
shared_ptr的工作机制是使用引用计数,每一个shared_ptr指向相同的对象(内存),所以很显然,只有最后一个指向该对象的shared_ptr指针不需要再指向该对象时,这个shared_ptr才会去析构所指向的对象。
(1)常规初始化
std::shared_ptr<std::string> p1; //默认初始化,智能指针里面保存的是一个空指针nullptr(可以指向类型为string的对象)。
std::shared_ptr<std::string> p2 = new std::string("abc"); //错误,智能指针是被explicit修饰的,不支持隐式转换(带等号一般为隐式转换)
std::string *str = new std::string("abc");
std::shared_ptr<std::string> p3 (str); //虽然裸指针可以初始化智能指针,但是不推荐智能指针和裸指针穿插使用
std::shared_ptr<std::string> p4(new std::string("abc")); //正确
(2)make_shared
make_shared函数这是一个标准库里的函数模板,被认为是最安全和更高效的分配和使用shared_ptr智能指针的一个函数模板。它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared_ptr。
注意:使用make_shared方法生成shared_ptr对象,那就没有办法自定义删除器了。
std::shared_ptr<int> p = std::make_shared<int>(10);
(3)引用计数
(3.1)引用计数增加
每个shared_ptr都关联着一个引用计数,当在下述几种情况下,所有指向这个对象的shared_ptr引用计数都会增加1。
- 用p1智能指针来初始化p2智能指针,就会导致所有指向该对象(内存)的shared_ptr引用计数全部增加1。
std::shared_ptr<A> p2 = std::make_shared<A>(10, 10);
std::shared_ptr<A> p3(p2); //也可写成这样:auto p3(p2);
std::cout << p3.use_count() << std::endl; //2
- 把智能指针当成实参往函数里传递(函数执行完毕后,这个指针的引用计数会恢复
)。但是如果传递引用作为形参进来,则引用计数不会增加。
void Test(std::shared_ptr<int> num)
{
std::cout << num.use_count() << std::endl; //3
}
void main()
{
std::shared_ptr<int> p = std::make_shared<int>(10); //1
auto p1(p);//2
Test(p1); //3
std::cout << p1.use_count() << std::endl; //2
}
void Test(std::shared_ptr<int>& num)
{
std::cout << num.use_count() << std::endl; //2
}
void main()
{
std::shared_ptr<int> p = std::make_shared<int>(10);//1
auto p1(p);//2
Test(p1);
}
- 作为函数的返回值并且有变量接收。
std::shared_ptr<int> Test(std::shared_ptr<int>& num) //注意是引用
{
std::cout << num.use_count() << std::endl; //2
return num;
}
void main()
{
std::shared_ptr<int> p = std::make_shared<int>(10);
auto p1(p);
auto p2 = Test(p1);
std::cout << p1.use_count() << std::endl; //3
}
(3.2)引用计数减少
- 给shared_ptr赋一个新值,让该shared_ptr指向一个新对象。
- 局部的shared_ptr离开其作用域。
- 当一个shared_ptr引用计数变为0,它会自动释放自己所管理的对象。
(4)shared_ptr指针常用操作
(4.1)use_count成员函数
该成员函数用于返回多少个智能指针指向某个对象。该成员函数主要用于调试目的,效率可能不高。
(4.2)unique成员函数
是否该智能指针独占某个指向的对象,也就是若只有一个智能指针指向某个对象,则unique返回true,否则返回false
(4.3)reset成员函数
- 当reset不带参数时。若pi是唯一指向该对象的指针,则释放pi指向的对象,将pi置空。若pi不是唯一指向该对象的指针,则不释放pi指向的对象,但指向该对象引用计数会减1,同时将pi置空。
- 当reset带参数(一般是一个new出来的指针)时。若pi是唯一指向该对象的指针,则释放pi指向的对象,让pi指向新内存。若pi不是唯一指向该对象的指针,则不释放pi指向对象,但指向该对象的引用计数会减1,同时让pi指向新内存。
- 空指针也可以通过reset来重新初始化。
(4.4)get成员函数
p.get():返回智能指针p中保存的指针。小心使用,若智能指针释放了所指向的对象,则返回的这个指针所指向的对象也就变得无效了。
为什么要有这样一个函数呢?主要是考虑到有些函数的参数需要的是一个内置指针(裸指针),所以需要通过get取得这个内置指针并传递给这样的函数。但要注意,不要delete这个get到的指针,否则会产生不可预料的后果。
(4.5)swap成员函数
用于交换两个智能指针所指向的对象。当然,因为是交换,所以引用计数并不发生变化。
(5)自定义删除器
智能指针能在一定的时机自动删除它所指向的对象。默认情况下,shared_ptr正是使用delete运算符作为默认的删除它所指向的对象的方式。
程序员可以指定自己的删除器,这样当智能指针需要删除一个它所指向的对象时,它不再去调用默认的delete运算符来删除对象,而是调用程序员为它提供的删除器来删除它所指向的对象。常规情况下,默认的删除器工作的是挺好,但是有一些情况需要自己指定删除器,因为默认的删除器处理不了——用shared_ptr管理动态数组的时候,就需要指定自己的删除器,默认的删除器不支持数组对象。
其实,定义数组时在尖括号“<>”中都加“[]”,即使不写自己的删除器,也能正常释放内存。
std::shared_ptr<int[]> p4(new int[10]{0});
shared_ptr指定删除器的方法比较简单,一般只需要在参数中添加具体的删除器函数名即可(注意,删除器是一个单形参的函数)。删除器也可以是一个lambda表达式。
void My_Deleter(int* a)
{
delete a;
}
void main()
{
///自定义删除器 lambda表达式形式
std::shared_ptr<int> p4(new int(10), [](int *p) {
delete p;
});
///自定义删除器 函数指针形式
std::shared_ptr<int> p5(new int(10), My_Deleter);
}
还有一点值得注意:就算是两个shared_ptr指定的删除器不相同,只要它们所指向的对象相同,那么这两个shared_ptr也属于同一个类型。同一个类型有个明显的好处是可以放到元素类型为该对象类型的容器里,方便操作。
std::vector<std::shared_ptr<int>> vec{ p4,p5 };
(6)使用建议、陷进与禁忌
(6.0)优先使用make_shared构造智能指针
优先使用make_shared构造智能指针,编译器内部会有一些针对内存分配的特殊处理,所以会使make_shared效率更高,如消除重复代码、改进安全性等。
std::shared_ptr<std::string> p1(new std::string("Hello world"));
上面这行代码会至少分配两次内存,第一次是为string类型的实例分配内存(从而保存字符串"Hello world"),第二次是在shared_ptr构造函数中给该shared_ptr控制块分配内存。
针对make_shared,编译器只会分配一次内存,这个内存分配的足够大,既能保存字符串("Hello world")又能够同时保存控制块:
std::shared_ptr<std::string> p2 = std::make_shared<std::string>("Hello world");
(6.1)慎用裸指针
把一个普通裸指针绑到了一个shared_ptr上,那内存管理的责任就交给了这个shared_ptr,这时就不应该再使用裸指针(内置指针)访问shared_ptr指向的内存了。
裸指针虽然可以初始化shared_ptr,但注意不要用裸指针初始化多个shared_ptr。即便用裸指针,直接传递new运算符而不是传递一个裸指针变量。
(6.2)慎用get返回的指针
永远不要用get得到的指针来初始化另外一个智能指针或者给另外一个智能指针赋值。
(6.3)用enable_shared_from_this返回this
enable_shared_from_this是一个类模板,它的类型模板参数就是继承它的子类的类名。该类模板中有一个弱指针weak_ptr,这个弱指针能够观测this,调用shared_from_this方法的时候,这个方法内部实际是调用了这个weak_ptr的lock方法,lock方法会让shared_ptr指针计数+1,同时返回这个shared_ptr。
(6.4)避免互相循环引用
互相循环引用会导致内存泄漏。
(7)移动语义
在shared_ptr智能指针里,也存在移动语义的概念。复制会使shared_ptr的强引用计数递增,而移动并不会使shared_ptr的强引用计数递增。
void main()
{
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = std::move(p1);
std::cout << p1.use_count() << std::endl; //0
std::cout << p2.use_count() << std::endl; //1
}
2.weak_ptr
weak_ptr这个智能指针是用来辅助shared_ptr工作的。weak翻译成中文是“弱”的意思,弱和强是反义词,那“强”指的又是谁呢?容易想象,强指的就是shared_ptr,弱指的就是weak_ptr。
将weak_ptr绑定到shared_ptr上并不会改变shared_ptr的引用计数(更确切地说,weak_ptr的构造和析构不会增加或者减少所指向对象的引用计数)。当shared_ptr需要释放所指向的对象时照常释放,不管是否有weak_ptr指向该对象。这就是weak“弱”的原因——能力弱(弱共享/弱引用:共享其他的shared_ptr所指向的对象),控制不了所指向对象的生存期。
弱引用可以理解成是监视shared_ptr(强引用)的生命周期用的,是一种对shared_ptr的扩充,不是一种独立的智能指针,不能用来操作所指向的资源,所以它看起来像是一个shared_ptr的助手(旁观者)这种感觉。所以它的智能也就智能在能够监视到它所指向的对象是否存在了。当然还有些额外用途。
shared_ptr指向的对象代表的引用统统指的都是强引用,而weak_ptr所指向的对象代表的引用统统都是弱引用。程序员是不能使用weak_ptr来直接访问对象的,必须要使用一个叫作lock的成员函数,lock的功能就是检查weak_ptr所指向的对象是否还存在,如果存在,lock能够返回一个指向共享对象的shared_ptr(当然原shared_ptr引用计数会+1),如果不存在,则返回一个空的shared_ptr。
std::shared_ptr<int> p6 = std::make_shared<int>(10); // shared_ptr指向的对象代表的引用统统指的都是强引用
std::weak_ptr<int> w_ptr(p6); // weak_ptr所指向的对象代表的引用统统都是弱引用,p6引用计数还是1
auto temp = w_ptr.lock(); //如果w_ptr所指的对象存在,并且有temp接收,p6引用计数加1
if (temp)
{
std::cout << p6.use_count() << std::endl; //2
}
(1)weak_ptr常用操作
(1.1)use_count成员函数
获取与该弱指针共享对象的其他shared_ptr的数量,或者说获得当前所观测资源的引用计数(强引用计数)。
(1.2)expired成员函数
是否过期的意思,若该指针的use_cout为0(表示该弱指针所指向的对象已经不存在),则返回true,否则返回false。换句话说,判断所观测的对象(资源)是否已经被释放。
(1.3)reset成员函数
将该弱引用指针设置为空,不影响指向该对象的强引用数量,但指向该对象的弱引用数量会减1。
(1.4)lock成员函数
获取所监视的shared_ptr。
(2)尺寸问题
weak_ptr尺寸(就是大小或者sizeof)和shared_ptr对象尺寸一样大,是是裸指针的2倍。在当前Visual Studio的x86平台下,一个裸指针的sizeof值是4字节。weak_ptr和shared_ptr的尺寸都是8字节,这8字节中包含了两个裸指针:
- 第一个裸指针指向的是该智能指针所指向的对象。
- 第二个裸指针指向一个很大的数据结构(控制块)。这个控制块里面有 :
- 所指对象的引用计数。
- 所指对象的弱引用计数。
- 其他数据,如自定义的删除器的指针(如果指定了自定义删除器)等。
控制块实际是由shared_ptr创建出来的,而后,当使用shared_ptr对象创建weak_ptr对象时,weak_ptr对象也指向了这个控制块。
(3)控制块
控制块是跟着类的,它的大小可能十几字节甚至更多,如指定了删除器等,那么这里的字节数可能会稍微变大一些。这个控制块是由第一个指向某个指定对象的shared_ptr来创建。因为weak_ptr对象也是通过shared_ptr创建出来的,因此weak_ptr对象也使用这个由shared_ptr创建的控制块。
控制块创建的时机:
- make_shared:分配并初始化一个对象,返回指向此对象的shared_ptr。所以make_shared总是创建一个控制块。
- 使用裸指针来创建一个shared_ptr对象时。(前面讲解shared_ptr使用陷阱时强调,不要用裸指针初始化多个shared_ptr,否则会产生多个控制块,也就是多个引用计数,导致析构所指向的对象时会析构多次,彻底混乱,导致程序运行异常。)
3.unique_ptr
谈到使用智能指针,一般来说,最先想到和优先考虑选择使用的还是unique_ptr智能指针。unique_ptr智能指针是一种独占式智能指针,或者理解成专属所有权这种概念也可以,也就是说,同一时刻,只能有一个unique_ptr指针指向这个对象(这块内存)。当这个unique_ptr被销毁的时候,它所指向的对象也会被销毁。
(1)常规初始化
std::unique_ptr<A> p = new A(10,10); //错误,智能指针被explicit修饰,不支持隐式转换(带等号一般为隐式转换)
std::unique_ptr<A> p(new A(10,10)); //正确
A* a = new A(10, 10); //不推荐智能指针和裸指针穿插用
std::unique_ptr<A> p1(a);
(2)make_unique
C++11中没有make_unique函数,但是C++14里提供了这个函数。与常规初始化比,也是要优先选择使用make_unique函数,这代表着更高的性能。当然,和make_shared一样,如果想使用删除器,那么就不能使用make_unique函数,因为make_unique不支持指定删除器的语法。
std::unique_ptr<A> pp = std::make_unique<A>(10, 10); //C++14,推荐
(3)常用操作
(3.1)移动语义
unique_ptr不允许复制、赋值等动作,是一个只能移动不能复制的类型。可以通过std::move来将一个unique_ptr转移到其他的unique_ptr。
unique_ptr智能指针不能复制。但有一个例外,如果这个unique_ptr将要被销毁,则还是可以复制的,最常见的是从函数返回一个unique_ptr。
std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");
std::unique_ptr<std::string> uni_ptr2 = std::move(uni_ptr1);//转移后,uni_ptr1为空
(3.2)release成员函数
放弃对指针的控制权(切断了智能指针和其所指向的对象之间的联系),返回指针(裸指针),将智能指针置空。返回的这个裸指针可以手工delete释放,也可以用来初始化另外一个智能指针,或者给另外一个智能指针赋值。
std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");
std::string *str = uni_ptr1.release();
delete str;
(3.3)reset成员函数
当reset不带参数时,释放智能指针指向的对象,并将智能指针置空。当reset带参数时,释放智能指针原来所指向的内存,让该智能指针指向新内存。
(3.4)get成员函数
返回智能指针中保存的对象(裸指针)。小心使用,若智能指针释放了所指向的对象,则返回的对象也就变得无效了。
(3.5)转换成shared_ptr类型
如果unique_ptr为右值,就可以将其赋给shared_ptr。模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr,shared_ptr将接管原来归unique_ptr所拥有的对象。
转换方法可通过函数返回unique_ptr,并使用shared_ptr来接收的方式;也可以通过std::move方法。
一个shared_ptr创建的时候,它的内部指针会指向一个控制块。把unique_ptr转换成shared_ptr的时候,系统也会为这个shared_ptr创建控制块。因为unique_ptr并不使用控制块,只有shared_ptr才使用控制块。
(4)尺寸问题
unique_ptr也基本和裸指针一样:足够小,操作速度也够快。但是,如果增加了删除器,那unique_ptr的尺寸可能不变化,也可能有所变化。
- 如果删除器是lambda表达式这种匿名对象,unique_ptr的尺寸就没变化。
- 如果删除器是一个函数,unique_ptr的尺寸就会发生变化。
unique_ptr尺寸变大肯定对效率有一定影响,所以把一个函数当作删除器,还是要慎用。这一点与shared_ptr不同,shared_ptr是不管指定什么删除器,其大小都是裸指针的2倍。
(5)删除器
unique_ptr的删除器相对复杂一点,先要在类型模板参数中传递进去删除器类型名,然后在参数中再给具体的删除器名。
创建shared_ptr的时候,删除器不同,但指向类型(所指向对象的类型)相同的shared_ptr,可以放到同一个容器中。但到了unique_ptr这里,如果删除器不同,则就等于整个unique_ptr类型不同,那么,这种类型不同的unique_ptr智能指针没有办法放到同一个容器中去的。
void My_Deleter(int* a)
{
delete a;
a = nullptr;
}
void main()
{
///自定义删除器 lambda表达式形式
using fun = void(*)(int*);
std::unique_ptr<int,fun> p4(new int(10), [](int *p) {
delete p;
p = nullptr;
});
///自定义删除器 函数指针形式
typedef void(*fun)(int*);
std::unique_ptr<int,fun> p5(new int(10), My_Deleter);
}
四、智能指针的选择
如果程序中要使用多个指向同一个对象的指针,应选择shared_ptr。
如果程序中不需要多个指向同一个对象的指针,则可使用unique_ptr。
总之,在选择的时候,优先考虑使用unique_ptr,如果unique_ptr不能满足需求,再考虑使用shared_ptr。