一种是类似这样:
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:
BPtr b;
~A () {
printf ("A released\n");
}
};
class B {
public:
APtr a;
~B () {
printf ("B released\n");
}
};
int main () {
APtr a(new A());
BPtr b(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:
BWeakPtr b; // 注意这里
~A () {
printf ("A released\n");
}
};
class B {
public:
AWeakPtr a; // 注意这里
~B () {
printf ("B released\n");
}
void output () {
printf ("I'm B\n");
}
};
int main () {
APtr a(new A());
BPtr b(new B());
a->b = b;
b->a = a;
BPtr b2(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 可以随便使用。
完美DEMO:
在std::shared_ptr被引入之前,C++标准库中实现的用于管理资源的智能指针只有std::auto_ptr一个而已。std::auto_ptr的作用非常有限,因为它存在被管理资源的所有权转移问题。这导致多个std::auto_ptr类型的局部变量不能共享同一个资源,这个问题是非常严重的哦。因为,我个人觉得,智能指针内存管理要解决的根本问题是:一个堆对象(或则资源,比如文件句柄)在被多个对象引用的情况下,何时释放资源的问题。何时释放很简单,就是在最后一个引用它的对象被释放的时候释放它。关键的问题在于无法确定哪个引用它的对象是被最后释放的。std::shared_ptr确定最后一个引用它的对象何时被释放的基本想法是:对被管理的资源进行引用计数,当一个shared_ptr对象要共享这个资源的时候,该资源的引用计数加1,当这个对象生命期结束的时候,再把该引用技术减少1。这样当最后一个引用它的对象被释放的时候,资源的引用计数减少到0,此时释放该资源。下边是一个shared_ptr的用法例子:
#include <memory>
class Woman;
class Man{
private:
std::weak_ptr<Woman> _wife;
//std::shared_ptr<Woman> _wife;
public:
void setWife(std::shared_ptr<Woman> woman){
_wife = woman;
}
void doSomthing(){
if(_wife.lock()){
}
}
~Man(){
std::cout << "kill man\n";
}
};
class Woman{
private:
//std::weak_ptr<Man> _husband;
std::shared_ptr<Man> _husband;
public:
void setHusband(std::shared_ptr<Man> man){
_husband = man;
}
~Woman(){
std::cout <<"kill woman\n";
}
};
int main(int argc, char** argv){
std::shared_ptr<Man> m(new Man());
std::shared_ptr<Woman> w(new Woman());
if(m && w) {
m->setWife(w);
w->setHusband(m);
}
return 0;
}
<span style="font-size:12px;">#include <iostream>
#include <memory>
class Woman;
class Man{
private:
std::weak_ptr<Woman> _wife;
//std::shared_ptr<Woman> _wife;
public:
void setWife(std::shared_ptr<Woman> woman){
_wife = woman;
}
void doSomthing(){
if(_wife.lock()){
}
}
~Man(){
std::cout << "kill man\n";
}
};
class Woman{
private:
//std::weak_ptr<Man> _husband;
std::shared_ptr<Man> _husband;
public:
void setHusband(std::shared_ptr<Man> man){
_husband = man;
}
~Woman(){
std::cout <<"kill woman\n";
}
};
int main(int argc, char** argv){
std::shared_ptr<Man> m(new Man());
std::shared_ptr<Woman> w(new Woman());
if(m && w) {
m->setWife(w);
w->setHusband(m);
}
return 0;
}</span>
在Man类内部会引用一个Woman,Woman类内部也引用一个Man。当一个man和一个woman是夫妻的时候,他们直接就存在了相互引用问题。man内部有个用于管理wife生命期的shared_ptr变量,也就是说wife必定是在husband去世之后才能去世。同样的,woman内部也有一个管理husband生命期的shared_ptr变量,也就是说husband必须在wife去世之后才能去世。这就是循环引用存在的问题:husband的生命期由wife的生命期决定,wife的生命期由husband的生命期决定,最后两人都死不掉,违反了自然规律,导致了内存泄漏。
解决std::shared_ptr循环引用问题的钥匙在weak_ptr手上。weak_ptr对象引用资源时不会增加引用计数,但是它能够通过lock()方法来判断它所管理的资源是否被释放。另外很自然地一个问题是:既然weak_ptr不增加资源的引用计数,那么在使用weak_ptr对象的时候,资源被突然释放了怎么办呢?呵呵,答案是你根本不能直接通过weak_ptr来访问资源。那么如何通过weak_ptr来间接访问资源呢?答案是:在需要访问资源的时候weak_ptr为你生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法就是lock()方法。
细节:shared_ptr实现了operator bool() const方法来判断一个管理的资源是否被释放。