目录
编译方法:
代码里需要加上头文件 #include <memory>
加上-std=c++11或者更高的版本,比如-std=c++14
编译范例:g++ -o test test.cpp -std=c++11
1 为什么需要智能指针
智能指针主要解决以下问题:
-
内存泄漏:内存手动释放,使用智能指针可以自动释放(malloc -> free; new -> delete)
-
共享所有指针的传播和释放,比如多线程使用同一个对象时析构问题
C++里面的四个智能指针:auto_ptr,shared_ptr,unique_ptr,weak_ptr其中后三个是C++11支持的,并且第一个已经被C++11弃用。
特点:
-
unique_ptr独立对象的所有权,由于没有引用计数,因此性能较好。
-
shared_ptr共享对象所有权,但性能略差。
-
weak_ptr配合shared_ptr,解决循环引用的问题。
2 shared_ptr共享的智能指针
2.1 shared_ptr的内存数据结构
shared_ptr内部包含两个指针,一个指向对象,另一个指向控制块(control block),控制块中包含一个引用计数(reference count),一个弱计数(weak count)和其它一些数据。
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。最后一个shared_ptr析构的时候,内存才会被释放。
shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象被销毁时,被管理对象自动销毁。
shared_ptr的实现包含了两部分:
- 一个指向堆上创建的对象的裸指针,raw_ptr
- 一个指向内部隐藏的、共享的管理对象。share_count_object
2.2 shared_ptr的基本用法和常数函数
s.get():返回shared_ptr中保存的裸指针。
s.reset(...):重置shared_ptr。
-
reset()不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空;若智能指针P不是唯一指向该对象的指针,则引用计数减少1,同时将P置空。
-
reset()带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若P不是唯一的指针,则只减少引用计数,并指向新的对象。
s.use_count():返回shared_ptr的强引用计数/
s.unique():若use_count()为1,返回true,否则返回false。
2.2.1 shared_ptr的初始化(make_shared和reset)
std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2 = p1;
std::shared_ptr<int> p3;
p3.reset(new int(1));23
我们应该优先使用make_shared来构造智能指针,因为他更高效。
-
内存分配和对象初始化是一次性完成的。使用make_shared时,内存和对象的初始化是在同一块连续内存中完成的。这意味着只需要进行一次动态内存分配,而且不会出现多个引用计数器或控制块。
-
减少动态内存分配的次数。使用make_shared可以减少动态内存分配的次数,因为多个智能指针可能会引用相同的对象。如果每个智能指针都使用new来分配内存,则会导致许多小型、不必要的堆上分配,从而影响程序性能。而使用make_shared则可以将这些堆上分配合并成一个大型块,并且可以重复利用已经存在的块以提高性能。
auto sp1 = make_shared<int>(100);
shared_ptr<int> sp1 = make_shared<int>(100);
//等价于
shared_ptr<int> sp1(new int(100));
不能将一个原始指针直接赋值给一个智能指针,例如,下面这种方法是错误的:
std::shared_ptr<int> p = new int(400);
shared_ptr不能通过“直接将原始指针这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。
-
对于一个未初始化的智能指针,可以通过reset()方法来初始化;
-
当智能指针有值的时候调用reset()会引起引用计数减1;
另外智能指针可以通过重载的bool类型操作符来判断:
#include <iostream>
#include <memory>
using namespace std;
int main() {
std::shared_ptr<int> ptr1(new int(10));
std::shared_ptr<int> ptr2;
if (!ptr1) { // 不输出
cout << "ptr1 is nullptr" << endl;
}
if (!ptr2) { // 输出 "ptr2 is nullptr"
cout << "ptr2 is nullptr" << endl;
}
ptr2 = ptr1;
if (ptr2) { // 打印 "ptr2 is not nullptr"
cout << "ptr2 is not nullptr" << endl;
}
return 0;
}
2.2.2 获取原始指针
当需要取原始指针时,可以通过get()方法来返回原始指针。
std::shared_ptr<int> ptr(new int(1));
int *p = ptr.get(); // 不小心delete p
谨慎使用ptr.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。
ptr.get()的返回值就相当于一个裸指针的值,不恰当的去使用这个值,上述陷阱的所有错误都有可能发生,遵循以下几个约定:
-
不要保存ptr.get()的返回值,无论是保存为裸指针还是保存为shared_ptr都是错误的
-
保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生独立指针
-
不要delete ptr.get()的返回值,会导致对一块内存delete两次的错误
2.2.3 指定删除器
如果使用shared_ptr管理非new对象或者是没有析构函数的类时,应当为其传递适合的删除器。
#include <iostream>
#include <memory>
using namespace std;
void DeleteIntPtr(int *p) {
cout << "DeleteIntPtr" << endl;
delete p;
}
int main() {
std::shared_ptr<int> ptr(new int(1), DeleteIntPtr);
return 0;
}
运行结果:
DeleteIntPtr
当ptr的引用计数为0时,自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达式,上面的写法可以改为:
#include <iostream>
#include <memory>
using namespace std;
int main() {
std::shared_ptr<int> ptr(new int(1), [](int *p){
cout << "lambda" << endl;
delete p;
});
return 0;
}
运行结果:
lambda
当我们用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数组对象,代码如下所示:
std::shared_ptr<int> ptr(new int[10], [](int *p) { delete [] p;});
2.2.4 使用shared_ptr要注意的问题
1 不要用一个原始指针初始化多个shared_ptr
#include <iostream>
#include <memory>
using namespace std;
int main() {
int *ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);
cout << "p1.use_count() " << p1.use_count() << endl;
cout << "p2.use_count() " << p2.use_count() << endl;
return 0;
}
运行结果:
p1.use_count() 1
p2.use_count() 1
free(): double free detected in tcache 2
Aborted (core dumped)
2 不要再函数实参中创建shared_ptr
function(shared_ptr<int>(new int), g()); //有缺陷
因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果此时g()恰好发生异常,而shared_ptr还没有创建,则int内存泄露了,正确的写法应该是先创建智能指针,代码如下:
shared_ptr<int> p(new int);
function(p, g());
3 通过shared_from_this()返回this指针
不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构。
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
std::shared_ptr<A> getSelf()
{
return std::shared_ptr<A> (this);
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
std::shared_ptr<A> p1(new A);
std::shared_ptr<A> p2 = p1->getSelf();
return 0;
}
运行结果:
~A()
~A()
free(): double free detected in tcache 2
Aborted (core dumped)
在这个例子中,由于用同一个指针(this)构造了两个智能指针p1和p2,而它们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。
正确返回this的shared_ptr的做法是:让目标类通过std::enable_shared_from_this类,然后使用基类的成员函数shared_from_this()来返回this的shared_ptr,如下所示:
#include <iostream>
#include <memory>
using namespace std;
class A: public std::enable_shared_from_this<A>
{
public:
std::shared_ptr<A> getSelf()
{
return shared_from_this();
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
std::shared_ptr<A> p1(new A);
std::shared_ptr<A> p2 = p1->getSelf();
return 0;
}
运行结果:
~A()
在weak_ptr中会讲解使用shared_from_this()的原因。
4 避免循环引用
循环引用会导致内存泄漏
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
std::shared_ptr<B> bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main() {
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
cout << "ap.use_count() " << ap.use_count() << endl;
cout << "bp.use_count() " << bp.use_count() << endl;
cout << "ap->bptr.use_count() " << ap->bptr.use_count() << endl;
cout << "bp->aptr.use_count() " << bp->aptr.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "ap.use_count() " << ap.use_count() << endl;
cout << "bp.use_count() " << bp.use_count() << endl;
cout << "ap->bptr.use_count() " << ap->bptr.use_count() << endl;
cout << "bp->aptr.use_count() " << bp->aptr.use_count() << endl;
}
cout << "main leave" << endl;
return 0;
}
运行结果:
ap.use_count() 1
bp.use_count() 1
ap->bptr.use_count() 0
bp->aptr.use_count() 0
ap.use_count() 2
bp.use_count() 2
ap->bptr.use_count() 2
bp->aptr.use_count() 2
main leave
循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不会减为0,导致两个指针都不会被析构,产生内存泄漏。
解决的办法是把A和B任何一个成员变量改为weak_ptr,具体方法见weak_ptr的讲解。
3 unique_ptr独占的智能指针
-
unique_ptr是一个独占型的智能指针,不能将其赋值给另一个智能指针
-
unique_ptr可以指向一个数组
-
unique_ptr需要确定删除器的类型
unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个智能指针。
std::unique_ptr<int> up(new int(1));
std::unique_ptr<int> up2 = up; // 不可以复制,会报错
std::shared_ptr<int> sp = up; // 不可以复制,会报错
std::weak_ptr<int> wp = up; // 不可以复制,会报错
std::unique_ptr<int> up3 = std::move(up); //可以
std::shared_ptr<int> sp1 = std::move(up); //可以
std::weak_ptr<int> wp1 = std::move(up); //不可以
unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移其它的unique_ptr,这样它本身就不再拥有原来指针的所有权了。
std::unique_ptr<int> up1(new int(1));
std::unique_ptr<int> up2 = std::move(up1);
std::make_shared是c++11的一部分,但std::make_unique不是,它是在c++14 里加入标准库的。
auto upw1(std::make_unique<int>(1));
std::unique_ptr<int> upw2(new int(2));
除了unique_ptr的独占性,unique_ptr和shared_ptr还有一些区别,比如:
-
unique_ptr可以指向一个数组
std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
std::shared_ptr<int []> ptr2(new int[10]); // 这个是不合法的
-
uniqeu_ptr指定删除器和shared_ptr有区别
std::shared_ptr<int> ptr1(new int(1), [](int *p){delete p;}); // 正确
std::unique_ptr<int> ptr2(new int(1), [](int *p){delete p;}); // 错误
unique_ptr需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器,可以这样写:
std::unique_ptr<int, void(*)(int *)> ptr2(new int(1), [](int *p){delete p;}); // 正确
关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择:
如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。
4 weak_ptr弱引用的智能指针
- 为什么要有weak_ptr?
shared_ptr虽然已经很好用了,但是有一点shared_ptr智能指针还是有内存泄漏的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。weak_ptr设计的目的是为了配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,他只可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起计数的增加或减少。
- weak_ptr为什么能解决问题?
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象,进行该对象的内存管理的是那个强行引用的shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段。
4.1 weak_ptr的基本用法
1 构造,只能用shared_ptr和weak_ptr构造,unique_ptr和原始指针会报错
std::shared_ptr<int> sp(new int(1));
std::weak_ptr<int> wp = sp;
std::weak_ptr<int> wp1(sp);
std::weak_ptr<int> wp2 = wp;
std::weak_ptr<int> wp3(wp);
2 通过use_count()方法获取当前观察资源的引用计数
std::shared_ptr<int> sp(new int(1));
std::weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; // 结果输出1,不会使引用计数增加
3 通过expired()方法判断所观察资源是否已经释放
std::shared_ptr<int> sp(new int(1));
std::weak_ptr<int> wp(sp);
if (wp.expired()) // true
cout << "已经释放" << endl;
else // false
cout << "未释放" << endl;
4 通过lock()方法获取监视的shared_ptr(一定要 先lock()再expired() 顺序不能变)
std::weak_ptr<int> wp;
void func() {
auto spt = wp.lock(); // 即使其它线程释放了wp,但是我已经锁住了,所以还能使用
if (wp.expired()) // lock 之后相当于 use_count + 1
cout << "wp无效,资源已释放" << endl;
else
cout << "wp有效, *spt = " << *spt << endl;
}
int main() {
{
auto sp = std::make_shared<int>(66);
wp = sp;
func();
}
func();
return 0;
}
运行结果:
wp有效, *spt = 66
wp无效,资源已释放
4.2 返回this指针的问题是怎么解决的
shared_ptr中提到不能直接将this指针返回shared_ptr,需要通过继承std::enable_shared_from_this类,并通过其shared_from_this()方法来返回指针,其原因是std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观察this智能指针,调用shared_from_this()方法,其实是调用weak_ptr的lock()方法,将所观察的shared_ptr返回,再看看前面的范例
#include <iostream>
#include <memory>
using namespace std;
class A: public std::enable_shared_from_this<A>
{
public:
std::shared_ptr<A> getSelf()
{
return shared_from_this();
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
std::shared_ptr<A> p1(new A);
std::shared_ptr<A> p2 = p1->getSelf();
return 0;
}
运行结果:
~A()
在外面创建A对象的智能指针和通过对象返回this的智能指针都是安全的,因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针,在离开作用域之后,sp1的引用计数减为0,A对象会被析构,不会出现A对象被析构两次的问题。
需要注意的是,获取自身智能指针的函数要在shared_ptr的构造函数被调用之后才能使用,因为std::enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。
4.3 weak_ptr解决引用循环问题
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
std::weak_ptr<B> bptr; // 修改为std::weak_ptr
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main() {
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
cout << "ap.use_count() " << ap.use_count() << endl;
cout << "bp.use_count() " << bp.use_count() << endl;
cout << "ap->bptr.use_count() " << ap->bptr.use_count() << endl;
cout << "bp->aptr.use_count() " << bp->aptr.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "ap.use_count() " << ap.use_count() << endl;
cout << "bp.use_count() " << bp.use_count() << endl;
cout << "ap->bptr.use_count() " << ap->bptr.use_count() << endl;
cout << "bp->aptr.use_count() " << bp->aptr.use_count() << endl;
}
cout << "main leave" << endl;
return 0;
}
运行结果:
ap.use_count() 1
bp.use_count() 1
ap->bptr.use_count() 0
bp->aptr.use_count() 0
ap.use_count() 2
bp.use_count() 1
ap->bptr.use_count() 1
bp->aptr.use_count() 2
B is deleted
A is deleted
main leave
这样在对B的成员赋值时,即执行ap->bptr=bp;时,由于bptr是weak_ptr,它并不会增加引用计数,所以bp的引用计数仍然会是1,在离开作用域之后,bp的引用计数会减为0,B对象会被析构,析构后其内部的aptr的引用计数会被减为1,然后在A离开作用域后ap的引用计数又从 1减为0,A对象也被析构,不会发生内存泄漏。
4.4 weak_ptr使用注意事项
1 weak_ptr在使用前需要检查合法性
std::weak_ptr<int> wp;
{
std::shared_ptr<int> sp(new int(1)); // sp.count() == 1;
wp = sp; // wp不会改变引用计数,所以sp.use_count() == 1
std::shared_ptr<int> sp1 = wp.lock(); // wp没有重载->操作符。只能这样取所指向的对象
}
std::shared_ptr<int> sp_null = wp.lock(); // sp_null.use_count() == 0;
因为上述代码中sp和sp1离开了作用域,其容纳的对象已经被释放了。所以sp_null的值为空。 在使用wp前需要调用wp.expired()函数判断一下。虽然wp的引用计数为0,但是wp仍旧存在,仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr一直保存着最后观察的那个指针的状态。
2 如果wp和sp1在同一作用域
#include <iostream>
#include <memory>
using namespace std;
int main() {
std::weak_ptr<int> wp;
std::shared_ptr<int> sp1;
{
std::shared_ptr<int> sp(new int(1));
wp = sp;
sp1 = wp.lock();
}
if (wp.expired())
cout << "shared_ptr is destory" << endl;
else
cout << "shared_ptr is not destory" << endl;
return 0;
}
运行结果:
shared_ptr is not destory
5 智能指针安全性问题
为什么多线程读写 shared_ptr 要加锁?csdn 陈硕
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接,详细查看详细的服务