【STL学习】智能指针之shared_ptr

前面已经学习过auto_ptr,这里补充另外一种智能指针,比auto_ptr要更强力更通用的shared_ptr。

shared_ptr

简介及使用选择 

几乎所有的程序都需要某种形式的引用计数智能指针,这种指针让我们不再需要为两个对象或更多对象共享的对象的生命周期而编写复杂的逻辑(写起来有点绕口),当被共享的对象引用计数降为0时,被共享对象被自动析构。

引用计数指针分为插入式(instrusive)和非插入式(non-instrusive)两种。前者要求它所管理的类提供明确的函数或数据成员用于管理引用计数,这要求类在设计时即预料到将与一个插入式引用计数指针一起工作,或者重新设计它。非插入式引用计数指针对它管理的类没有任何要求,引用计数智能指针拥有与它所存指针有关的内存的所有权。

引用计数智能指针可以自动管理生存周期,避免共享对象拥有者之间有太强的耦合,增加了程序的重用性。

以下场景更适合应用引用计数智能指针:类的复制很昂贵,或者它代表的有些东西必须被多个实例共享;共享的资源没有一个明确的拥有者。

引用计数智能指针的优点:引用计数智能指针可以再需要访问共享对象的多个资源之间共享访问权;引用计数智能指针还能让你把对象存入标准库的容器中而不存在泄漏的风险;如果把指针放入容器可以获得多态的好处(模板即一种静态多态计数),可以提高性能(这个不敢苟同,多态的引入更多是为了代码的重用而不是为了效率,甚至可能会牺牲部分效率),可以把相同的对象放入多个容器进行特定的查找(这是一个很好的主意,提高查找的效率并且不管生命周期)。

使用插入式引用计数指针还是非插入式引用计数指针:通常选择非插入式引用计数指针,因为他更灵活、更通用,不需要修改已有的代码。

shared_ptr的关键函数

	template <class U> explicit shared_ptr (U* p);

这个构造函数获得指定指针p的所有权,p必须是一个合法的指针,否则报错,构造完成后引用计数count=1

	template <class U, class D> shared_ptr (U* p, D del);

这个构造函数包含两个参数,p是将要被管理的指针,del是被销毁时负责释放资源的对象,被保存的对象将以del(p)的形式传给del

	template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;

x中保存的资源被新构造的对象锁共享,引用计数count+1,貌似是唯一的通过构造共享对象的方式。

	template <class U> explicit shared_ptr (const weak_ptr<U>& x);

从一个weak_ptr构造shared_ptr,这使得weak_ptr的使用具有线程安全性,因为指向weak_ptr参数的共享资源引用计数将会+1(weak_ptr不影响共享资源的引用计数)。

	template <class U> shared_ptr (auto_ptr<U>&& x);

从auto_ptr中获取x保存的指针的所有权,方法是保存x保存的指针的一份拷贝并 对x调用release。

	~shared_ptr();

析构函数对引用计数count-1,如果计数为0,保存的指针被删除,删除操作采用operator delete 或者用给定的删除器对象,将指针作为唯一的参数传递给删除器对象。

operator =的重载版本

	template <class U> shared_ptr& operator= (const shared_ptr<U>& x) noexcept;

共享x中的资源

	template <class U> shared_ptr& operator= (shared_ptr<U>&& x) noexcept;

将共享资源的所有权从x转移出来,x变成一个空的shared_ptr,共享资源的引用计数不变。

想要给shared_ptr指定共享资源不能直接对指针赋值,可以调用make_shared或者reset替代。

	void reset() noexcept;

停止对保存指针所有权的共享,引用计数count-1

	template <class U> void reset (U* p);

获得p的所有权并设置引用计数为1,获得p的所有权之前默认调用析构函数,即先另之前拥有的共享对象引用计数count-1.

	element_type& operator*() const noexcept;

返回共享指针指向对象的引用。

	element_type* operator->() const noexcept;

返回保存的指针

	element_type* get() const noexcept;

返回保存的指针,当保存的指针可能为空时最好用get,而不是*或者->

	bool unique() const noexcept;

当shared_ptr是共享对象的唯一拥有者才返回为true,否则返回false

	long int use_count() const noexcept;

获取共享对象的引用计数,根据大神的建议,该函数最好只用于调试,因为引用计数的计算代价昂贵

	explicit operator bool() const noexcept;

检测保存的共享对象指针是否是一个空指针,如果是空指针就返回false,其他情况返回true。

	void swap (shared_ptr& x) noexcept;

交换两个shared_ptr保存的指针

	template <class T, class... Args>
 	 shared_ptr<T> make_shared (Args&&... args);

make_shared的使用略绕,构造一个T类型的共享对象,args就是T类构造函数的入参,T类型的共享对象通过new创建,创建以后引用计数count=1

allocate_shared的使用类比上面。

	template <class T, class U>
 	 shared_ptr<T> static_pointer_cast (const shared_ptr<U>& sp) noexcept;

当需要对保存的指针进行类型转换的时候调用,可以确保引用计数正确。

	template <class T, class U>
  	shared_ptr<T> dynamic_pointer_cast (const shared_ptr<U>& sp) noexcept;

动态转换保存的指针且确保引用计数正确

	template <class T, class U>
 	 shared_ptr<T> const_pointer_cast (const shared_ptr<U>& sp) noexcept;

const转换保存的指针且保证引用计数正确

shared_ptr使用的时机主要是用来解决被多个对象共享的资源的正确释放机会。

大神的一个示例很能说明问题,有两个类A和B,它们共享一个int实例

#include <cassert>

class A {

boost::shared_ptr<int> no_;

public:

A(boost::shared_ptr<int> no) : no_(no) {}

void value(int i) {

*no_=i;

}

};

class B {

boost::shared_ptr<int> no_;

public:

B(boost::shared_ptr<int> no) : no_(no) {}

int value() const {

return *no_;

}

};

int main() {

boost::shared_ptr<int> temp(new int(14));

A a(temp);

B b(temp);

a.value(28);

assert(b.value()==28);

}

类A和类B都保存了一个shared_ptr<int>,创建实例的时候,temp被传到它们的构造函数,这样同时有3个shared_ptr:a、b和temp,在例子中a、b、temp都离开main的作用域时,最后一个智能指针负责删除共享的int。

shared_ptr用标准库容器

把对象直接存入容器会有些麻烦,以值传递方式保存对象意味着调用者将获得值得一份拷贝,对于那些复制带价昂贵的类型来说可能会有性能问题,另外传值意味着没有多态的行为,如果想在容器中存放多态的对象而不想切割他们,那必须用指针,如果用裸指针,维护元素的完整性将十分复杂,使用shared_ptr可以不必担心多个使用者使用同一个元素,元素将在没有对象引用的时候被释放掉。

下面转一下大神的例子:将共享指针存入标准容器库,本来准备自己写一个例子的,可怎么样都超越不了这个经典的小例子。

class A {

public:

virtual void sing()=0;

protected:

virtual ~A() {};

};

class B : public A {

public:

virtual void sing() {

std::cout << "Do re mi fa so la";

}

};

boost::shared_ptr<A> createA() {

boost::shared_ptr<A> p(new B());

return p;

}

int main() {

typedef std::vector<boost::shared_ptr<A> > container_type;

typedef container_type::iterator iterator;

container_type container;

for (int i=0;i<10;++i) {

container.push_back(createA());

}

std::cout << "The choir is gathered: \n";

iterator end=container.end();

for (iterator it=container.begin();it!=end;++it) {

(*it)->sing();

}

}

这个例子的精彩之处在于同时实现了多态和共享指针的保护,多态很好理解,vector中存入的是类A的指针,通过指针实现了多态行为;共享指针的保护就堪称经典了,类A的析构函数设置为protect,这样就不能delete shared_ptr<A> get()来释放shared_ptr<A>指向的对象,手动delete释放shared_ptr<A>指向的对象将造成混乱,但类B的析构函数不是protect,shared_ptr在引用计数变为0的时候调用B的析构函数自动释放了对象。---其实这个例子里我不太明白shared_ptr是怎样正确调用B的析构函数的,内部实现肯定不是delete,也没见到传递析构器呀,问题保留,慢慢看源码分析。

shared_ptr与容器实现多态且保证安全性的一种方式:基类析构函数设置为protected。

shared_ptr与其他资源

有时候shared_ptr需要用于特殊的类型,需要其他的清理操作,而不是简单的delete,shared_ptr可以通过客户化删除器以支持这种需要,像处理FILE*这种操作系统句柄通常要用fclose来释放,这种时候我们可以定制一个客户化删除器或者传入一个单参函数来析构

class FileCloser {

public:

void operator()(FILE* file) {

std::cout << "The FileCloser has been called with a FILE*, "

"which will now be closed.\n";

if (file!=0)

fclose(file);

}

};

int main() {

std::cout <<  "shared_ptr example with a custom deallocator.\n";

{

FILE* f=fopen("test.txt","r");

if (f==0) {

std::cout << "Unable to open file\n";

throw "Unable to open file";

}

boost::shared_ptr<FILE>

my_shared_file(f, FileCloser());

// 定位文件指针

fseek(my_shared_file.get(),42,SEEK_SET);

}

std::cout << "By now, the FILE has been closed!\n";

}

上面的例子也可以用传递单参函数的方式实现

{

FILE* f=fopen("test.txt","r");

if (f==0) {

std::cout << "Unable to open file\n";

throw file_exception();

}

boost::shared_ptr<FILE> my_shared_file(f,&fclose);

// 定位文件指针

fseek(&*my_shared_file,42,SEEK_SET);

}

std::cout << "By now, the FILE* has been closed!\n";

定制删除器在处理需要特殊释放程序的资源时非常有用,由于删除器不是shared_ptr的一部分,所以使用者不需要知道有关智能指针所拥有的资源的任何信息,例如使用对象池,只是简单的将对象返还到对象池中。或者单例模式singleton应该使用什么都不做的删除器。

使用定制删除器的安全性

对基类使用pretected的析构函数可以增加shared_ptr的安全性,另一个同样安全的方法是声明析构函数为pretected或者private并使用一个定制删除器来负责销毁对象,这个定制删除器必须是要删除类的友元,封装这个删除器的好方法是把删除器类实现为私有的嵌套类。

class A {

class deleter {

public:

void operator()(A* p) {

delete p;

}

};

friend class deleter;

public:

virtual void sing() {

std::cout << "Lalalalalalalalalalala";

}

static boost::shared_ptr<A> createA() {

boost::shared_ptr<A> p(new A(),A::deleter());

return p;

}

protected:

virtual ~A() {};

};

int main() {

boost::shared_ptr<A> p=A::createA();

}

我们不能使用普通函数作为shared_ptr<A>的工厂函数,因为嵌套的删除器是A私有的,使用这个方法,用户不能在栈上创建A的对象,也不能对A的指针调用delete。

从this生成shared_ptr

有时候需要从this获得shared_ptr,也就是希望类被shared_ptr所管理,你需要把“this”变成shared_ptr的方法,我们可以用另一个智能指针weak_ptr来解决,weak_ptr是shared_ptr的一个观察者,它只是看着它们但不影响计数,通过存储一个指向this的weak_ptr作为类成员,就可以在需要的时候获得一个指向this的shared_ptr。

当以下情况时适合使用shared_ptr

多个使用者使用同一个对象,且没有明显的拥有者

要把指针存入标准库容器时

当要传送对象到库或者从库获取对象,且没有明显的拥有者

管理一些需要特殊清除方式的资源时。 

补充

本文来自Beyond the c++ standard library中文版,有兴趣更进一步学习的朋友情参见原版。

【STL学习】智能指针之shared_ptr

时间: 2024-09-28 20:39:09

【STL学习】智能指针之shared_ptr的相关文章

boost智能指针之shared_ptr和weak_ptr

std::auto_ptr很多的时候并不能满足我们的要求,比如auto_ptr不能用作STL容器的元素.boost的smart_ptr中提供了4种智能指针和2种智能指针数组来作为std::auto_ptr的补充. shared_ptr<boost/shared_ptr.hpp>:使用shared_ptr进行对象的生存期自动管理,使得分享资源所有权变得有效且安全. weak_ptr<boost/weak_ptr.hpp>:weak_ptr 是 shared_ptr 的观察员.它不会干

详解Boost库智能指针(shared_ptr &amp;&amp; scoped_ptr &amp;&amp; weak_ptr )

我们先来解释一下什么叫智能指针? 智能指针是利用RAII(在对象的构造函数中执行资源的获取(指针的初始化),在析构函数中释放(delete 指针):这种技法把它称之为RAII(Resource Acquisition Is Initialization:资源获取即初始化))来管理资源. 其本质思想是:将堆对象的生存期用栈对象(智能指针)来管理.也就是当new一个堆对象的时候,立刻用智能指针来接管,具体做法是在构造函数中进行初始化(用一个指针指向堆对象),在析构函数调用delete来释放堆对象.由

智能指针(二):shared_ptr实现原理

前面讲到auto_ptr有个很大的缺陷就是所有权的转移,就是一个对象的内存块只能被一个智能指针对象所拥有.但我们有些时候希望共用那个内存块.于是C++ 11标准中有了shared_ptr这样的智能指针,顾名思义,有个shared表明共享嘛.所以shared_ptr类型的智能指针可以做为STL容器的元素 下面我们来瞧瞧shared_ptr具体是咋实现的.相较auto_ptr有下面几个不同的地方: 1.引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块 2

智能指针tr1::shared_ptr、boost::shared_ptr使用

对于tr1::shared_ptr在安装vs同时会自带安装,但是版本较低的不存在.而boost作为tr1的实现品,包含 "Algorithms Broken Compiler Workarounds Concurrent Programming Containers Correctness and Testing Data Structures Domain Specific Function Objects and Higher-order Programming Generic Progra

C++ 智能指针(shared_ptr/weak_ptr)源码分析

C++11目前已经引入了unique_ptr, shared_ptr, weak_ptr等智能指针以及相关的模板类enable_shared_from_this等.shared_ptr实现了C++中的RAII机制,它不仅仅具有一般指针(build-in/raw)的特性,更重要的是它可以自动管理用户在堆上创建的对象的生命周期,让用户不用负责内存回收,避免内存泄漏.一般的智能指针都定义为一个模板类,它的类型由被管理的对象类型初始化,内部包含了指向该对象的指针以及指向辅助生命周期管理的管理对象的指针.

C++ 11智能指针之shared_ptr

 shared_ptr是一个引用计数智能指针,用于共享对象的所有权.它可以从一个裸指针.另一个shared_ptr.一个auto_ptr.或者一个weak_ptr构造.还可以传递第二个参数给shared_ptr的构造函数,它被称为删除器(deleter).删除器用于处理共享资源的释放,这对于管理那些不是用new分配也不是用delete释放的资源时非常有用.shared_ptr被创建后,就可以像普通指针一样使用了,除了一点,它不能被显式地删除.shared_ptr的比较重要的接口如下: tem

智能指针之 shared_ptr

 std::shared_ptr 是通过指针保持对象共享所有权的智能指针.多个 shared_ptr 对象可占有同一对象大概实现了一下,主要实现原理为,共享指针内部持有堆资源的指针以及引用计数的指针,通过对这两个指针的维护,达到多个共享对象对同一资源的控制 实现主要分为三个文件.share_ptr.h,smart_ptr_define.h, main.cpp  (编译平台:Linux centos 7.0 编译器:gcc 4.8.5 ) 1 //smart_ptr_define.h 2 #ifn

C++智能指针: auto_ptr, shared_ptr, unique_ptr, weak_ptr

本文参考C++智能指针简单剖析 内存泄露 我们知道一个对象(变量)的生命周期结束的时候, 会自动释放掉其占用的内存(例如局部变量在包含它的第一个括号结束的时候自动释放掉内存) int main () { { int a = 1; printf("%d\n", a); } { a = 2; printf("%d\n", a); } } 这样会编译错误. 但是如果这样写呢? void func(int &o) { int *p = new int(o); ret

关于智能指针类型shared_ptr的计数问题

一.关键 每个shared_ptr所指向的对象都有一个引用计数,它记录了有多少个shared_ptr指向自己 shared_ptr的析构函数:递减它所指向的对象的引用计数,如果引用计数变为0,就会销毁对象并释放相应的内存 引用计数的变化:决定权在shared_ptr,而与对象本身无关 二.引用计数初步 shared_ptr<int> sp; //空智能指针 shared_ptr<int> sp2 = make_shared<int>(3); shared_ptr<