您的位置:首页 > 编程语言 > C语言/C++

Effective C++之std::tr1::shared_ptr的使用

2014-05-14 19:11 507 查看
Effective C++之std::tr1::shared_ptr的使用

shared_ptr采用引用计数,多个指针可以指向同一个对象;auto_ptr就不能,只能运行一个指针指向一个对象:如果要指针赋值,那么原来的指针要放弃对该对象的所有权。

shared_ptr在最新的c++11中,已经被列入了标准指针,而auto_ptr则出局了。shared_ptr采用RAII技术,是防止内存泄露的神器。

看下面的程序,new了一个对象,并没有在程序中使用delete,但是,运行程序,其构造函数仍然运行!这就是shared_ptr,如果要预防内存泄露,它就是最佳选择!

# include <iostream>
# include <tr1/memory>
using namespace std;
class A {
public:
A() {
cout << "construct A!!!" << endl;
}
;
~A() {
cout << "destruct A!!!" << endl;
}
;
};
class B: public A {
public:
B() {
cout << "construct B!!!" << endl;
}
;
~B() {
cout << "destruct B!!!" << endl;
}
;
};
int main() {
//    B* ptrB0 = new B();
std::tr1::shared_ptr<B> ptrB1(new B);
}


运行结果:

construct A!!!

construct B!!!

destruct B!!!

destruct A!!!

在 c++ 98 里面只有一种智能指针,就是 std::auto_ptr,因为具有唯一所有权的特征,所以限制了它的使用范围,比如你无法在容器中使用它。而我们知道 stl 容器是值语义的,如果不能用智能指针管理的话,只有两种办法来使用。
一种是类似这样:

std::vector<std::string> names;

names.push_back("cyberscorpio");

std::string name("news818");

names.push_back(name);

每次向容器中添加内容的时候,实际上产生了该内容的另一份拷贝,对于简单的内容(或对象)来说,问题不大,但如果是很复杂的内容,很大的对象,调用拷贝构造函数的成本会比较高,同时亦不利于统一维护。

另一种做法就是在容器中存指针,这样当然就避免了上面的问题,但是,这需要你在容器对象销毁的时候,显式的释放容器中的每一个元素。略有些不方便,但还可以接受。

更大的问题在于,当程序比较复杂的时候,对象的生命周期管理对程序员来说,会是一个比较头疼的事情,比如你有一个指针(对象),在程序的很多地方都用到它,如果你在某个地方释放了它而没有通知它的其他使用者的话,就会造成非法的内存访问,从而程序崩溃。

shared_ptr 通过指针的引用计数,很好的解决了这个问题,和 COM 组件生存期管理机制类似,只有当引用计数为 0 的时候,才会释放这个对象。而 shared_ptr 不需要程序员手工调用 AddRef 和 Release 函数,进一步减小了出错的可能性。

但是,引用计数有一个麻烦,它不能解决所谓循环引用的问题,举个例子,有对象 A 和 B,A 和 B 中,各有一个智能指针指向对方。

#include <stdio.h>

#include <memory>

class A;

class B;

typedef std::tr1::shared_ptr<a> APtr;

typedef std::tr1::shared_ptr<b> BPtr; 

class A {

public:

  B  Ptrb;

  ~A () {

    printf ("A released/n");

  }

};

class B {

public:

  A  Ptra;

  ~B () {

    printf ("B released/n");

  }

};

int main () {

  A  Ptra(new A());

  B  Ptrb(new B());

  a->b = b; // 1

  b->a = a; // 2

  return 0;

}

我们编译运行这段程序,会发现 A 和 B 的析构函数都没有被调用,因为在只能指针 a 的生命周期结束的时候,它手中对 A 对象的引用计数还剩下 1 (即 b 手中的引用),所以对象 A 不会被释放。而指针 b 生命结束的时候哦,它手中 B 对象的引用也还有 1,导致没有对象被释放。

上面的例子里面,注释为 1 和 2 的两句,任意去掉一句,就打破了这个循环的引用,从而 A 和 B 都能正确的释放。那么,假如我真的需要 A 和 B 之间互相引用,难道就没有别的办法了吗?办法是有的,就是使用 std::tr1::weak_ptr。weak_ptr,顾名思义,是一个 “弱” 一点的智能指针,它不会增加引用计数,当你需要使用这个对象的时候,可以从 weak_ptr 临时生出一个 shared_ptr 来 (通过 lock 函数),这个临时的 shared_ptr 生命结束以后,就会把引用计数减小
1,这样就不会出现互相死锁的情况了。

#include <stdio.h>

#include <memory> 

class A; 

class B; 

typedef std::tr1::shared_ptr<A> APtr; 

typedef std::tr1::shared_ptr<B> BPtr; 

typedef std::tr1::weak_ptr<A> AWeakPtr; 

typedef std::tr1::weak_ptr<B> BWeakPtr; 

class A { 

public: 

  B  WeakPtrb; // 注意这里 

  ~A () { 

    printf ("A released/n"); 

  } 

};

class B { 

public: 

  A   WeakPtra; // 注意这里 

  ~B () { 

    printf ("B released/n"); 

  }

  void output () { 

    printf ("I'm B/n"); 

  } 

};

int main () { 

  A  Ptra(new A()); 

  B  Ptrb(new B());

  a->b = b; 

  b->a = a;

  B  Ptrb2(a->b.lock()); 

  b2->output();

  return 0; 



编译运行,我们会欣喜的看到,A 和 B 都正确的被释放了。

I'm B

B released

A released

所以,在使用 shared_ptr 第一个要注意的,就是在可能会引起循环引用的地方,使用 weak_ptr。

还有一个问题也很常见,当使用了 shared_ptr 的时候,我们可能需要在所有的地方都使用它,否则就不容易达到管理生存期的目的了。但有的时候,我们手头上只有对象的原始指针,比如在对象的函数内部,我们只有 this。这就迫切的需要一个功能:如何从对象的裸指针中,生成我们需要的 shared_ptr。

有人可能会觉得这个简单,shared_ptr<T> a(this); 不就行了么?很遗憾的告诉你,这样不行,会出问题。为什么呢?因为这里的 a,手中对 this 的引用计数只有 1,它无法知道其他地方智能指针对 this 这个指针(就是这个对象)的引用情况,因此当 a 的生命周期结束(比如函数返回)的时候,this 就会被它毫不留情的释放掉,其他地方的相关智能指针,手中拿着的该对象指针已经变成非法。

那么怎样解决呢?也很简单,使用 std::tr1::enable_shared_from_this 作为基类。比如:

class A : public std::tr1::enable_shared_from_this<A> 



public: 

  std::tr1::shared_ptr<A> getSharedPtr() { 

    return shared_from_this(); 

  } 

}; 

这样就可以从 this 中生出具有统一引用计数的 shared_ptr 了。只有一个问题是需要注意的,就是 getSharedPtr 函数,或者说 shared_from_this 不能在对象 A 的构造函数中调用。因为 enable_shared_from_this 这个基类的内部,是通过一个对自己的 weak_ptr 的引用来返回 this 的 shared_ptr 的,而在对象的构造函数中,第一个 shared_ptr 尚未获得对象的指针,所以 weak_ptr 是空的,直接导致 shared_from_this()
返回失败。除此以外,shared_from_this 可以随便使用。

另外shared_ptr解决了多个auto_ptr同时指向同一对象销毁时对象被删除一次以上的问题。tr1::shared_ptr是“引用技术型智慧指针”,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除资源。

请看下面实例:

#include <iostream>
#include <memory>
using namespace std;

class A {
public:
A() {
cout << "construct A!!!" << endl;
}
;
~A() {
cout << "destruct A!!!" << endl;
}
;
A* CreateA();
};
A* CreateA()
{
A * ptrA = new A;
return ptrA;
}
int _tmain(int argc, _TCHAR* argv[])
{
std::tr1::shared_ptr<A> ptrA1(new A);
std::tr1::shared_ptr<A> ptrA2(ptrA1);

cout<<"按任意键继续……";
cin.clear();
cin.sync();
cin.get();
return 0;
}


ptrA1和ptrA2其实指向的是同一个对象。所以会输出以下结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: