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

时间: 2024-12-28 12:15:05

smart pointer的相关文章

[C++] smart pointer

写在前面的话: 智能指针的设计意图:C++没有垃圾回收机制,所有的动态内存释放全部由程序员负责,如果程序员没有释放内存,就会造成内存泄漏,这是C++ BUG的一大来源.为了管理动态内存,引入了智能指针,它是一种行为类似指针的类,但是能够管理自己负责的内存区域,当对象离开作用域时能够释放内存,防止内存泄漏. 现在常用的C++标准是C++98,其中只定义了一种智能指针auto_ptr. boost中早就引入了其它类型的智能指针:scoped_ptr,scoped_array,shared_ptr,s

Smart pointer 智能指针小总结

Smart pointer line 58之后smart pointer里的计数已经是0,所以会真正释放它引用的对象,调用被引用对象的析构函数.如果继续用指针访问,会出现如下图的内存访问异常.所以说如果选择了用智能指针,就不要再试图用其他方式再去访问对象了. 1 // sharedTest.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include

C++ smart pointer智能指针

  在C++中,程序员可以直接操作内存,给编程增加了不少的灵活性.但是灵活性是有代价的,程序员必须负责自己负责释放自己申请的内存,否则就会出现内存泄露.智能指针就是为了解决这个问题而存在的.它和其他指针没有本质的区别,主要的目的就是为了避免悬挂指针.内存泄露的问题.在这里,我使用对象的应用计数做了一个smart pointer,当一个对象还有引用的时候,就不执行释放内存的操作,当引用计数为0时,就执行内存释放操作,并且将指针重置为NULL. 代码如下: #include <iostream>

My understanding about smart pointer

Here are two simple questions. Problem A #include <string> include <iostream> using namespace std; class vehicle { public: vehicle(const string& name); virtual ~vehicle(){} void PrintOwnerInfo(); private: string driver_name_; }; vehicle::v

[CareerCup] 13.8 Smart Pointer 智能指针

13.8 Write a smart pointer class. A smart pointer is a data type, usually implemented with templates, that simulates a pointer while also providing automatic garbage collection. It automatically counts the number of references to a SmartPointer<T*>

【C++】智能指针(Smart Pointer)

1. 传统指针存在的问题 传统指针存在诸多的问题,比如指针所指向的对象的生命周期问题,挂起引用(dangling references),以及内存泄露(memory leaks). 如下是一个传统指针的使用过程 void Foo() { int *iPtr = new int[5]; // manipulate the memory block // ... // ... // ... delete[] iPtr; } 以上代码将正常运行且内存将被合理释放,但是使用指针常会发生一些意想不到的事情

boost smart pointer

1. boost::scoped_ptr is a smart pointer that is the sole owner of a dynamically allocated object and cannot be copied or moved. #include <boost/scoped_ptr.hpp> #include <iostream> int main() { boost::scoped_ptr<int> p(new int(1)); std::c

智能指针(smart pointer)(2):unique_ptr

Unique pointer: Manages the storage of a pointer, providing a limited garbage-collection facility, with little to no overhead over built-in pointers (depending on the deleter used). These objects have the ability of taking ownership of a pointer: onc

智能指针(smart pointer)(1):auto_ptr

智能指针解决了资源生存期管理的问题(尤其是动态分配的对象).智能指针有各种不同的风格.多数都有一种共同的关键特性:自动资源管理.这种特性可能以不同的方式出现:如动态分配对象的生存期控制,和获取及释放资源 (文件, 网络连接).这里主要讨论第一种情况,它们保存指向动态分配对象的指针,并在正确的时候删除这些对象. 何时我们需要智能指针? 有三种典型的情况适合使用智能指针: ? 资源所有权的共享 ? 要写异常安全的代码时 ? 避免常见的错误,如资源泄漏 共享所有权,当多个对象需要同时使用第三个对象的情