标签(空格分隔): 学习笔记
传统指针存在诸多的问题,比如指针所指向的对象的生命周期问题,挂起引用(dangling references),以及内存泄露(memory leaks). 如下代码是一个传统指针的使用过程
void Foo(){ int * ptr = new int[5]; //... //... delete[] ptr;}以上代码将正常运行且内存被合理释放,但是在我们编写较为复杂的程序时,申请了过多的东戴指针和动态内存,往往会忘了delete,或者在错误的时间错误的地点提前delete掉了指针,这无疑是会导致程序崩溃或者内存泄露的。 智能指针是RAII(Resource Acquisition is initialization)用来动态的分配内存。它提供了普通指针的所有接口外加少数异常处理。在构造阶段,它将分配内存,而在非其作用域内将自动释放所占有的内存。 在C++98中,使用 auto_ptr来解决上述问题。
auto_ptr在所指对象作用域结束之后会自动调用其析构函数,这样就不用手动的delete,看如下代码:
#include <iostream>#include <memory>using namespace std;class autoPtrTest{public: //constructor function autoPtrTest(int a = 0) :m_a(a){} //deconstructor function ~autoPtrTest() { cout << "calling deconstructor" << endl; getchar(); }public: int m_a;};int main(){ //create an auto_ptr(p) to point a autoPtrTest class, and initialize with 5 auto_ptr<autoPtrTest> p(new autoPtrTest(5)); cout << p->m_a << endl; return 0;}运行结果如下图所示: 当类型为auto_ptr的指针p指向的对象作用域结束后,自动调用了该类的析构函数. 但是,使用auto_ptr时,无可避免的会出现以下几个问题,这是由auto_ptr自身的性质决定的 1)auto_ptr会传递它本身的ownership,当其被赋值给另一个auto_ptr对象。正如下述程序所示,一个auto_ptr对象传递给函数Fun()中的auto_ptr对象时,其ownership,或者说是smartness将不再返回给原auto_ptr所指向的p。
程序运行结果如下图所示: 这是因为在调用函数Fun时,main中创建的指针p在函数中将自己的所有权给了p1,所以离开p1的作用域时,编译器自动调用了析构函数,此时main中p为一个空指的野指针,所以程序报错。 2)auto_ptr不能使用于数组对象。 这里的意思是不能使用于操作符new[] 3)auto_ptr不能使用于一些标准的容器库。比如vector,list,map等等 C++11提出了新型的智能指针,并且都赋予了其相应的意图。
shared_ptr设计的目的很简单:多个共享指针可以指向同一个对象,而当最后一个共享指针在作用域范围内结束时,内存才会被自动的释放。
int main(){ // share_ptr 常规的创建过程 shared_ptr<int> sptr1(new int); // 使用make_shared 来加速创建过程 // shared_ptr 自动分配内存,并且保证引用计数 // 而make_shared则是按照这种方法来初始化 shared_ptr<int> sptr2 = make_shared<int>(100); // 可以通过use_count() 来查看引用计数 cout << "sptr2 referenced count: " << sptr2.use_count() << endl; shared_ptr<int> sptr3 = sptr2; cout << "sptr2 referenced count: " << sptr2.use_count() << endl; cout << "*sptr2 = " << *sptr2 << endl; getchar(); return 0;}上述代码的运行结果为: 上述代码创建了一个shared_ptr指针指向了一个装着整型值且值为100的内存块,并且引用计数为1,。当其他共享指针通过sptr1来创建时,引用计数将为2。
用户可以显式的调用函数,lambda表达式,函数对象来调用对于shared_ptr为数组对象的析构函数delete[]。 同时,shared_ptr为用户提供了以下方便接口: shared_ptr提供解引用*, 以及->来普通指针的相关操作。同时,还提供了以下的接口: get()
: To get the resource associated with the shared_ptr. reset()
: To yield the ownership of the associated memory block. If this is the last shared_ptrowning the resource, then the resource is released automatically. unique
: To know whether the resource is managed by only this shared_ptr instance. Operator bool
: To check whether the shared_ptr owns a memory block or not. Can be used with an if condition. 但是,shared_ptr同样也存在问题: 1)当一个内存块与shared_ptr绑定相关,并且属于不同组时,将会发生错误。所有的shared_ptr共享一个组的同一个共享引用。
以下表格给出了相应的引用计数:
pointer | count |
---|---|
shared_ptrstPR1(new int) | 1 |
shared_ptr sptr2 = sptr1 | 2 |
sptr3 = sptr2 | 3 |
when main ends->sptr3 goes out of scope | 2 |
sptr2 goes out of scope | 1 |
sptr1 goes out of scope | 0->the resources is released |
以上代码运行正常,然而当运行以下代码是:
int main(){ int *p = new int; shared_ptr<int>sptr1(p); shared_ptr<int>sptr2(p); return 0;}pointer | count |
---|---|
shared_ptrsptr1(p) | 1 |
shared_ptrsptr2(p) | 1 |
when main ends->sptr1 goes out of scope | 0 p is destroyed |
sptr2->goes out of scope | 0 and crash |
为了避免这种情况发生,最好不用从裸指针中建立共享指针。 2)问题2:另一个问题是,正如上述问题,如果从一个裸指针中创建一个共享指针,只有一个共享指针时,可以正常运行,但是当裸指针被释放时,共享指针也会crash。 3)循环引用时,如果资源被非恰当释放,也会出现问题。
#include <iostream>#include <memory>using namespace std;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;};int main(){ shared_ptr<B> sptrB(new B); shared_ptr<A> sptrA(new A); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; return 0;}当类A包含了指向B的共享指针,而类B包含了指向A 的恭喜那个指针时,sptrA和SptrB相关的资源都将不会被释放。结果如下图:
pointer | count |
---|---|
shared_ptr sptrB(new B) | 1 |
shared_ptr sptrA(new A) | 1 |
sptrB->m_sptrA = sptrA | sptrA->2 |
sptrA->m_sptrB = sptrB | sptrB->2 |
main ends->sptrA goes out of scope | sptrA->1 |
main ends->sptrB gos out of scope | sptrB->1 |
一个弱指针,提供的是一种共享语义定义而不是拥有语义定义。这就意味着一个弱指针可以通过shared_ptr共享资源。所以要创建弱指针,必须是已经拥有资源但是是一个共享指针。
一个弱指针并不允许诸如普通指针所提供的*和->。因为他并不是资源的拥有者。
那么如何利用弱指针呢? **weak_ptr只能用于跟踪一个共享的资源,但并不实际拥有,也不会阻碍资源的释放。读取共享资源前需要先执行lock,得到shared_ptr后才能进行访问。 当两个对象需要互相引用时,我们总希望其中一个对象拥有另一个对象的强引用,而另一个对象拥有自己的弱引用,如果两个对象都是强引用,则容易引起循环引用,导致两个对象都无法正确释放。**
用weak_ptr作为一个类似share_ptr但却能悬浮的指针 有一个矛盾,一个灵巧指针可以像shared_ptr 一样方便,但又不参与管理被指对象的所有权。换句话说,需要一个像shared_ptr但又不影响对象引用计数的指针。这类指针会有一个shared_ptr没有的问题:被指的对象有可能已经被销毁。一个良好的灵巧指针应该能处理这种情况,通过跟踪什么时候指针会悬浮,比如在被指对象不复存在的时候。这正是weak_ptr这类型灵巧指针所能做到的。
weak_ptr一般是通过shared_ptr来构造的。当使用shared_ptr来初始化weak_ptr时,weak_ptr就指向了相同的地方,但是不改变所指对象的引用计数。 从上图可以看书,通过将一个weak_ptr赋值给另一个时会增加其弱引用计数。
如果弱引用指针所指向的资源,被其共享指针所释放时,这时候弱指针将会过期。如何检测一个弱指针是否指向一个合法的资源呢?有以下两种途径。
调用use_count()
来得到引用计数。注意这里返回的是强引用计数。 调用expired()
函数,这比调用use_count要快的多。 同时,我们可以通过对一个weak_ptr调用函数lock()来得到一个shared_ptr。或者直接对一个weak_ptr进行强制转换。
以上方法将增加强引用计数 以下例子将展示如何使用weak_ptr解决循环引用问题
#include <iostream>#include <memory>using namespace std;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;}int main(){ shared_ptr<B> sptrB(new B); shared_ptr<A> sptrA(new A); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; sptrA->PrintSpB(); return 0;}unique_ptr几乎是易出错的auto_ptr的另一种形式。unique_ptr遵循专用所有权语义。在任何时刻,资源只被唯一的一个unique_ptr所占有。当auto_ptr不在作用域范围内时,资源就会被释放。当一个资源被其他资源重写时,如果先前的资源已经被释放,这保证了相关的资源也会被释放。 creation(创建) unique_ptr创建的过程和shared_ptr创建的过程大同小异,所不同的是创建的数组形式的对象。 unique_ptr提供了专用创建数组对象的析构调用delete[]而不是delete当其不在作用域范围内。
unique_ptr<int[]>unptr(new int[5]);对于资源的拥有权(ownership)可以从一个unique_ptr通过另一个进行赋值来传递。
需要记住的是:unique_ptr并不提供复制机制copy semantics(包括复制赋值copy assignment以及复制构造函数copy construction)而是一种移动机制。
Interface(接口)
unique_ptr提供的接口与普通常规指针的接口非常相似,但是并不提供指针运算。 unique_ptr提供release()函数来进行yield the ownership。release()和reset()函数的区别在于,reset()会对资源进行销毁。
参考文献:
新闻热点
疑难解答
图片精选