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进行安全的訪问


posted @ 2017-07-05 16:25  lytwajue  阅读(364)  评论(0编辑  收藏  举报