smart pointer

Posted xero10

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了smart pointer相关的知识,希望对你有一定的参考价值。

smart pointer是一种abstract data type,它能够模仿指针的行为,并且额外提供了一系列诸如自动内存管理、边界检查等特性,这些特性是为了在保证效率的基础上减少由于对指针的不正常使用而带来的bug。smart pointer能够自动进行object的销毁:当某个object的最后一个拥有者被destroy的时候(如局部变量离开了作用域),由smart pointer管理的object会被自动销毁。smart pointer是被声明在stack中的

在C++中,smart pointer是以operator overload的方式模仿传统的指针(raw pointer)而用template class实现的

smart pointer在内存和性能方面被设计得尽可能的高效,例如,unique_ptr中唯一的data member就是封装好的指针,这意味着unique_ptr的大小与那个指针完全相同(4/8 Byte)。通过smart pointer中被重载的"->"和"*"访问被封装的指针并不会比直接访问raw pointer慢很多


smart pointer通常会提供一个直接访问它包含的raw pointer的方法,如get()的member function,从而可以实现既通过smart pointer进行内存管理,又可以将raw pointer传递给不支持smart pointer的代码

some_type* foo();	// we don't know if caller should delete the memory when it no more needs the data
// by declaring the return type to "unique_ptr", it's made explicit that caller owns the result
unique_ptr<some_type> foo();	// prior to C++11, "unique_ptr" can be replaced with "auto_ptr"


为什么要使用smart pointer

1. 自动清理:不需要释放内存

2. 自动初始化:会自动被初始化为NULL

3. 避免了dangling pointer的问题:释放后会自动置为NULL

4. 出现exception时会自动释放内存,而不必在catch中进行delete

5. 垃圾回收(并没有在背景中单独运行的垃圾回收机制,而是通过标准的C++ scope rules进行内存管理,所以更快速高效)

6. 高效:能更高效地使用内存来缩短allocation和de-allocation的时间

7. 支持STL:比如要用vector来存放base class和derived class,如果用vector<base>会发生slicing problem,所以应该用一个vector<base*>,而这样的话在使用完vector要逐个地手动释放其中的元素,会很麻烦。这时使用smart pointer可以自动释放(但是要注意某些smart pointer可能不能用于STL container)



unique_ptr

定义在头文件<memory>中,它是raw pointer的container,并且拥有这个raw pointer。unique_ptr显式地禁止了对所包含的pointer的复制(例如通过"="),需要通过"std::move()"函数来将所包含的指针的拥有权转移给另一个unique_ptr。如果令一个unique_ptr被指向一个新的资源,则原来指向的资源会被自动释放。unique_ptr不能进行复制因为它的copy constructor和赋值运算符都被显式地删除了。当unique_ptr离开作用域时,如果声明的对象是数组,则delete[]会自动取代delete被调用。unique_ptr的大小就是一个指针的大小,并且它支持rvalue reference来实现对STL的快速插入和获取

unique_ptr的接口和普通指针类似,但不允许对指针进行数学计算。unique_ptr提供了release()和reset(),二者的区别是release()只是交出了对资源的所有权但并未销毁资源,而reset()会销毁资源

std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1; // compile error.
std::unique_ptr<int> p3 = std::move(p1); // transfers ownership. p3 now owns the memory and p1 is rendered invalid.

p3.reset(); // deletes the memory.
p1.reset(); // does nothing.


int *p0 = new int(5);
std::unique_ptr<int> pa(p0);
std::unique_ptr<int> pb(p0);	// runtime error
"std::auto_ptr"仍然存在,但在C++11中已被弃用。"auto_ptr"的copy constructor和赋值运算符都并非对存储的指针进行复制,而是实际进行了移动,运行后会使原来的auto_ptr指向的object成为空。然而在以前的代码中会存在用auto_ptr中进行复制的情景,所以无法将其改变为只进行移动的指针而不改变对以前代码的向后兼容性


class LargeObject

public:
    void DoSomething()
;

void ProcessLargeObject(const LargeObject& lo)
void SmartPointerDemo()
    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

 //pLarge is deleted automatically when function block goes out of scope.
上例展示了使用smart pointer的几个必要步骤:

1. 将smart pointer声明为自动(局部)变量,而不要对smart pointer本身使用new或malloc

2. 在type parameter中,声明所指向的被封装好的指针的类型

3. 传递一个raw pointer或new的object给smart pointer的constructor

4. 使用被重载过的"->"、"*"来访问object

5. 让smart pointer删除object


shared_ptr

C++11定义了shared pointer:"std::shared_ptr",在头文件<memory>中。"shared_ptr"也是raw pointer的container,它通过维护它所包含pointer的reference-counted所有权而与其他所有shared_ptr的副本进行协作。只有当所有shared_ptr的副本都被销毁时,被它所包含的raw pointer所指向的object才会被销毁。当想要将一个raw pointer赋给多个拥有者时,使用shared_ptr。shared_ptr的大小是两个指针,一个指向object,另一个指向包含reference count的shared control block

std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; // both now own the memory.

p1.reset(); // memory still exists, due to p2.
p2.reset(); // deletes the memory, since no one else owns the memory.

shared_ptr的使用可能会出现问题,例如:

void main( )

 int* p = new int;
 shared_ptr<int> sptr1(p);
 shared_ptr<int> sptr2(p);
上面的代码会产生问题,因为不同group的两个shared_ptr共享了同一个资源。如果用delete释放了p指向的空间,则离开scope时,shared_ptr会再次释放已被释放的空间。另一个问题是也是资源可能被重复释放,如下图:


所以,最好不要用raw pointer创建多个shared_ptr。下面的代码就没有问题:

void main( )

 shared_ptr<int> sptr1(new int);
 shared_ptr<int> sptr2 = sptr1;
 shared_ptr<int> sptr3;
 sptr3 = sptr2;

shared_ptr另一个可能的问题是circular reference而导致资源无法被释放,如下例:

class B;
class A

public:
	A() : m_sptrB(nullptr) ;
	~A() 
		cout<<" A is destroyed"<<endl;
	
	shared_ptr<B> m_sptrB;
;

class B

public:
	B() : m_sptrA(nullptr) ;
	~B( ) 
		cout<<" B is destroyed"<<endl;
	
	shared_ptr<A> m_sptrA;
;
//***********************************************************
void main()

	shared_ptr<B> sptrB(new B);
	shared_ptr<A> sptrA(new A);
	sptrB->m_sptrA = sptrA;
	sptrA->m_sptrB = sptrB;


具体过程如下图:



所以,使用shared_ptr可能会出现以下问题:

1. 如果一个memory block与多个不同group的shared_ptr相关联,则会出现错误。所有共享同一个reference的shared_ptr必须属于同一个group

2. 用raw pointer创建shared_ptr可能会出现问题

3. circular reference



weak_ptr

C++11在头文件<memory>中定义了weak pointer:"weak_ptr",它也是raw pointer的container,可以解决上述shared_ptr可能出现的问题。weak pointer只能提供sharing semantics而不能提供owning semantics,意味着它要和shared_ptr一起使用,它被创建为一个shared_ptr的副本,并且只能共享已经被shared_ptr拥有的资源。weak_ptr副本的存在和销毁对相应的shared_ptr和这个shared_ptr的其他副本没有影响,因为它并不参与strong reference counting。如果一个shared_ptr的所有副本都已经被销毁,则这个shared_ptr对应的weak_ptr副本会变成空

weak pointer不提供普通指针所支持的接口(如"*","->"),因为它不是资源的拥有者进而不会给程序猿任何错误使用它的机会。如果要使用weak pointer指向的资源,需要通过weak_ptr创建shared_ptr,因为这样保证resource不会被销毁(因为对strong reference count加了1),否则可能在使用weak_ptr时,shared_ptr拥有的资源已被释放。要用weak_ptr产生shared_ptr,可以调用lock()或直接将weak_ptr转换成shared_ptr

另外当想要观察一个object但并不要求这个object一直有效时,也可以使用weak_ptr


weak_ptr的constructor将shared_ptr作为它的一个参数,用一个shared_ptr创建weak_ptr增加了shared_ptr的weak reference counter,另外将一个weak_ptr赋给另一个weak_ptr也会增加weak reference count。

std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; // p1 owns the memory.


  std::shared_ptr<int> p2 = wp1.lock(); // create shared_ptr from weak_ptr. now p1 and p2 own the memory.
  if(p2) // since p2 is initialized from a weak pointer, you have to check if the memory still exists!
  
    // do something with p2
  
 // p2 is destroyed. Memory is owned by p1.

p1.reset(); // memory is deleted.

std::shared_ptr<int> p3 = wp1.lock(); // memory is gone, so we get an empty shared_ptr.
if(p3)

  // will not execute this.

由于shared_ptr的实现使用了reference counting,circular references可能会成为问题。一个circular shared_ptr chain可以通过将其中的一个reference更改为weak_ptr来打破,如下例:

class B;
class A

public:
	A() : m_a(5) ;
	~A() 
		cout<<" A is destroyed"<<endl;
	
	void PrintSpB( );
	weak_ptr<B> m_sptrB;
	int m_a;
;

class B

public:
	B() : m_b(10) ;
	~B() 
		cout<<" B is destroyed"<<endl;
	
	weak_ptr<A> m_sptrA;
	int m_b;
;

void A::PrintSpB( ) 
	if( !m_sptrB.expired() ) 
		cout<< m_sptrB.lock()->m_b<<endl;
	


void main( )

	shared_ptr<B> sptrB(new B);
	shared_ptr<A> sptrA(new A);
	sptrB->m_sptrA = sptrA;
	sptrA->m_sptrB = sptrB;
	sptrA->PrintSpB(); 


当一个weak_ptr指向的资源被shared_ptr释放时,weak pointer就expire了。有两种方法检查weak pointer是否expire:

1. 调用use_count(),返回strong reference数

2. 调用expired(), 这比调用use_count()更快


多个线程可以同时地通过不同的shared_ptr和weak_ptr而对同一个object进行安全的访问


以上是关于smart pointer的主要内容,如果未能解决你的问题,请参考以下文章

Smart pointer 智能指针小总结

smart_pointer example

boost smart pointer

Smart Pointer Programming Techniques

Smart Pointer Programming Techniques

std::vector of objects / pointers / smart pointers to pass objects(总线错误:10)?