本文主要是介绍C++:共享指针(shared_ptr)详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
shared_ptr是C++11提供的另外一种常见的智能指针,与unique_ptr独占对象所有权不同,shared_ptr允许多个指针指向同一个对象。
每个shared_ptr对象都有一个关联的计数器,被称为引用计数,用来记录有多少个shared_ptr指向所管理的内存对象。这个计数器是线程安全的。每当多一个智能指针一个对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向该对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。
创建shared_ptr对象
std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2=std::make_shared<int>(2);
默认初始化的智能指针中保存着一个空指针
共享对象
我们可以通过拷贝和赋值操作实现多个shared_ptr共享一个资源对象
std::shared_ptr<int> p2(new int(2));
std::shared_ptr<int> p3=p2;
当拷贝一个shared_ptr时,对于被拷贝的shared_ptr所指向的对象来说,其引用计数会增加,通常来说有3种常见的情况:
- 使用一个shared_ptr去初始化另一个shared_ptr,会拷贝参数的shared_ptr对象。
- 将它作为函数参数,传递给一个函数时。
- 将它作为函数返回值,也会发生拷贝。
计数器较少的情况:
- 当shared_ptr销毁时,比如离开其作用域,会触发其析构函数,这时所管理对象的引用计数会减一。
- 当给shared_ptr赋予一个新值时,其原来所指向的对象的引用计数会减一
指定删除器
使用shared_ptr管理非new对象或者是没有析构函数的类时,应该为其传递合适的删除器
#include <iostream>#include <memory>using namespace std;void DeleteIntPtr(int *p) {cout << "call DeleteIntPtr" << endl;delete p;}int main(){std::shared_ptr<int> p(new int(1), DeleteIntPtr);std::shared_ptr<int> p2(new int(1), [](int *p) {cout << "call lambda1 delete p" << endl;delete p;});//p3没有显式指定类型为数组类型int[],shared_ptr默认调用delete//而非delete[]来删除他管理的对象,为了正确删除,需要自定义删除器std::shared_ptr<int> p3(new int[10], [](int *p) {cout << "call lambda2 delete p" << endl;delete [] p; // 数组删除});return 0;}
智能指针什么时候需要指定删除器:
在需要 delete 以外的析构行为的时候用. 因为 shared_ptr 在引用计数为 0 后默认调用 delete ptr; 如果不满足需求就要提供定制的删除器.
一些场景:
- 资源不是 new 出来的(一般也意味着不能 delete), 比如可能是 malloc 出来的
- 资源是被第三方库管理的 (第三方提供 资源获取 和 资源释放 接口, 那么要么写一个 wrapper 类要么就提供定制的 deleter)
- 资源不是 RAII 的, 意味着析构函数不会把资源完全释放掉...也就是单纯 delete 还不够, 还得做额外的操作比如你的 end_connection 的例子. 虽然我觉得这个是 bad practice
循环引用
//定义A,拥有B类型指针
class A {
public:std::shared_ptr<B> pb;~A() {std::cout << "~A" << std::endl;}
};//定义B,拥有A类型的指针
class B {
public:std::shared_ptr<A> pa;~B() {std::cout << "~B" << std::endl;}
};
void Test(){std::shared_ptr<A> pA= std::make_shared<A>();std::shared_ptr<B> pB= std::make_shared<B>();//pA内部指向pBpA->pb = pB;//pb内部执行papB->pa = pA;
}int main(){//会导致循环引用,2个堆内存对象无法被释放Test();return 0;
}
Test函数结束时,局部变量的销毁是按照其创建顺序的相反顺序来进行销毁的。pB先于pA销毁。当pB销毁时,会先调用pB的析构函数,它会检测到它所指向的对象有2个引用者,即pB和pA的成员pb,引用计数为2,离开作用域后,pB的引用计数-1,并不是0,跟据shared_ptr的规则,pB所指向的内存不会被释放。pA同理。pA和pB所指向的内存都没有得到释放,会发生内存泄漏。
解决方法是将A或B的成员设定为weak_ptr
weak_ptr
weak_ptr也是一种智能指针,通常配合shared_ptr
一起使用。
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。所以需要使用一个shared_ptr来初始化一个weak_ptr,并且将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
它的最大特点是:一旦最后一个指向对象的shared_ptr被销毁,该对象就会被销毁,即使还有weak_ptr指向该对象,所有weak_ptr都会变成nullptr
。
所以,有时会出现weak_ptr还指向着对象,但是该对象已经被销毁了的情况。不能直接通过weak_ptr访问其所指向的对象。我们以利用expired()
方法来判断这个weak_ptr
是否已经失效。
我们可以通过weak_ptr
的lock()
方法来获得一个指向共享对象的shared_ptr
。如果weak_ptr
已经失效,lock()
方法将返回一个空的shared_ptr
。
std::shared_ptr<int> p1= std::make_shared<int>(2);std::weak_ptr<int> wp(p1);// 通过lock创建一个对应的shared_ptr
if (auto p = wp.lock()) {std::cout << "shared_ptr value: " << *p << std::endl;std::cout << "shared_ptr use_count: " << p.use_count() << std::endl;
} else {std::cout << "wp is expired" << std::endl;
}// 释放shared_ptr指向的资源,此时weak_ptr失效
p1.reset();
std::cout << "wp is expired: " << wp.expired() << std::endl;
注意事项
不要混用普通指针和智能指针
void TestShared(std::shared_ptr<int> p) {...}//离开作用域时,p会被销毁int main()
{int* p1 = new int(2);TestShared(std::shared_ptr<int> (p1));//指向的对象已经被delete,p1是一个空悬指针std::cout << *p1<< std::endl;return 0;
}
对象的引用计数是其shared_ptr的个数,当一个共享对象的shared_ptr为0时,即使有普通指针还在指向它,也会被释放。
不要使用使用get初始化另一个智能指针或为智能指针赋值。
std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2(p1.get());
std::cout << p1.use_count() << " " << p2.use_count() <<std:: endl;
打印会发现它们的引用计数为1,因为引用计数是分开计数的,当其中一类的shared_ptr的引用计数为0时,就会释放对象内存,这时其他shared_ptr就是空悬指针了,此时会出现double free问题。
性能
-
内存占用高
shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。
因此相比于 unique_ptr, shared_ptr 的内存占用更高 -
原子操作性能低
考虑到线程安全问题,引用计数的增减必须是原子操作。而原子操作一般情况下都比非原子操作慢。 -
使用移动优化性能
shared_ptr 在性能上固然是低于 unique_ptr。而通常情况,我们也可以尽量避免 shared_ptr 复制。
如果,一个 shared_ptr 需要将所有权共享给另外一个新的 shared_ptr,而我们确定在之后的代码中都不再使用这个 shared_ptr,那么这是一个非常鲜明的移动语义。
对于此种场景,我们尽量使用 std::move,将 shared_ptr 转移给新的对象。因为移动不用增加引用计数,性能比复制更好。
template<class T>
class SharedPtr{
private:T* m_p;int* m_count;void clear() {if(m_count&& --(*m_count)==0){delete m_p;m_p=nullptr;delete m_count;m_count=nullptr;}}public:SharedPtr(T* ptr=nullptr):m_p(p),m_count(new int(1)){}~SharedPtr(){clear();}//拷贝构造SharedPtr(const SharedPtr& that):m_p(that.m_p),m_count(that.m_count){++(*m_count);}//拷贝赋值SharedPtr& operator=(const SharedPtr& that){if(m_p!=that.m_p){clear();m_p=that.m_p;m_count=that.m_count;++(*m_count); }return *this;}//移动构造SharedPtr(SharedPtr&& that):m_p(that.m_p),m_count(that.m_count) {that.m_p=nullptr;that.m_count=nullptr;}//移动赋值SharedPtr& operator=(SharedPtr&& that){clear();m_p=that.m_p;m_count=that.m_count;that.m_p=nullptr;that.m_count=nullptr;return *this;}T& operator*(){return *m_p;}T* operator->(){return m_p;}int get_count() const {return *m_count;}};
这篇关于C++:共享指针(shared_ptr)详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!