Skip to content

Instantly share code, notes, and snippets.

@BruceChen7
Last active November 29, 2020 11:58
Show Gist options
  • Save BruceChen7/8cccb33ea6fbc73a0651c4bce3166806 to your computer and use it in GitHub Desktop.
Save BruceChen7/8cccb33ea6fbc73a0651c4bce3166806 to your computer and use it in GitHub Desktop.
[#shared_ptr] #cpp #智能指针 #shared_ptr

enable_shared_from_this:

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

这样使用是不对的,sp1和sp2两者互相不知道对方,所以当离开作用域的时候,会释放两遍。 同样的道理:如果一个成员函数返回this的shared_ptr,也会出现上面的问题。成员函数dangerous是不会了解外面的sp1已经拥有了shared_ptr的资源。

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

正确的编写dangerous的姿势是如下:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}
class Example : public boost::enable_shared_from_this < Example >{
public:
	int x;
	Example() :x(233){}
	Example(int _x) :x(_x){}
};
int main(){
	for (;;){
		boost::shared_ptr<Example> sp = boost::make_shared<Example>(666);
		assert(sp->x == 666);
		boost::shared_ptr<Example> p = sp->shared_from_this();
		assert(p.use_count() == 2);
		assert(p->x == 666);
	}
}

但是要注意的是not_dangerous正确的调用方式是,shared_ptr包裹新创建的对象后创建

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}

常见的shared_ptr的使用方式:

std::shared_ptr<int> p(new int);  // or '=shared_ptr<int>(new int)' if you insist
auto p = std::make_shared<int>(); // or 'std::shared_ptr<int> p' if you insist

两者的区别和联系:

  • 第一种要分配内存两次,一次给管理的对象,另一个是给引用计数,
  • make_shared则是只有一次内存。

基本的使用方式

int* p = new int(233);
boost::shared_ptr<int> sp(p);
//然后就不能再delete p了, 否则重复释放

boost::shared_ptr<int> sp;
assert(!sp);

boost::shared_ptr<int> sp;
assert(!sp);
int* p = new int(233);
sp.reset(p);
assert(sp);
assert(233 == *sp);

防止可能的泄露

void f(shared_ptr<int>, int);
int g();
void ok(){
    shared_ptr<int> p( new int(2) );
    f( p, g() );
}
void bad(){
    f( shared_ptr<int>( new int(2) ), g() );
}

防止循环引用

class Humen{};
class Male:public Humen{
	boost::shared_ptr<Humen> _wife;
};
class Female:public Humen{
	boost::shared_ptr<Humen> _husband;
};
class SingleDog:public Humen{
};

但我真需要一个mate怎么办? boost还有weak_ptr.

wear_ptr是一个弱化的shared_ptr, 它的存在不会阻碍析构, 而且可以从一个有效的wear_ptr中获取对应的shared_ptr。weak_ptr相当于一个旁观者, 它并不提供完整的指针操作, 如果你要使用其管理的对象, 必须取其shared_ptr.

将以上代码换成如下便可破解循环引用:

class Human{
public:
	Human(){};
	boost::weak_ptr<Human> _mate;
};

boost::weak_ptr

上面已经提到了weak_ptr, 可以看到, weak_ptr是为了配合shared_ptr而引入的一种智能指针, 它更像shared_ptr的一个助手而不是智能指针。

Linux多线程服务端编程例子

假设有 Stock 类,代表一只股票的价格。每一只股票有一个唯一的字符串标识,比如 Google 的 key 是 "NASDAQ:GOOG",IBM 是 "NYSE:IBM"。Stock 对象是个主动对象,它能不断获取新价格。为了节省系统资源,同一个程序里边每一只出现的股票只有一个 Stock 对象,如果多处用到同一只股票,那么 Stock 对象应该被共享。如果某一只股票没有再在任何地方用到,其对应的 Stock 对象应该析构,以释放资源,这隐含了“引用计数”。

// version 1: questionable code
class StockFactory : boost::noncopyable
{
public:
    shared_ptr<Stock> get(const string& key);
private:
    mutable MutexLock mutex_;
    std::map<string, shared_ptr<Stock> > stocks_;
};

get() 的逻辑很简单,如果在 stocks_ 里找到了 key,就返回 stocks_[key];否则新建一个 Stock,并存入 stocks_[key]。细心的读者或许已经发现这里有一个问题,Stock 对象永远不会被销毁,因为map 里存的是 shared_ptr,始终有“铁丝”绑着。

// // version 2: 数据成员修改为 std::map<string, weak_ptr<Stock> > stocks_;
shared_ptr<Stock> StockFactory::get(const string& key)
{
    shared_ptr<Stock> pStock;
    MutexLockGuard lock(mutex_);
    weak_ptr<Stock>& wkStock = stocks_[key]; // 如果 key 不存在,会默认构造一个
    pStock = wkStock.lock(); // 尝试把“棉线”提升为“铁丝”
    if (!pStock) {
        pStock.reset(new Stock(key));
         wkStock = pStock; // 这里更新了 stocks_[key],注意 wkStock 是个引用
    }
    return pStock;
}

这么做固然 Stock 对象是销毁了,但是程序却出现了轻微的内存泄漏,为什么? 因为 stocks_ 的大小只增不减,stocks_.size() 是曾经存活过的 Stock 对象的总数,即便活的 Stock 对象数目降为 0。或许有人认为这不算泄漏,因为内存并不是彻底遗失不能访问了,而是被某个标准库容器占用了。我认为这也算内存泄漏,毕竟 是“战场”没有打扫干净。

其实,考虑到世界上的股票数目是有限的,这个内存不会一直泄漏下去,大不了把每只股票的对象都创建一遍,估计泄漏的内存也只有几兆字节。如果这是一个其他类型的对象池,对象的 key 的集合不是封闭的,内存就会一直泄漏下去。

解决的办法是,利用 shared_ptr 的定制析构功能。shared_ptr 的构造函数可以有一个额外的模板类型参数,传入一个函数指针或仿函数 d,在析构对象时执行d(ptr),其中 ptr 是 shared_ptr 保存的对象指针。shared_ptr 这么设计并不是多余的,因为反正要在创建对象时捕获释放动作,始终需要一个 bridge。

// version 3
class StockFactory : boost::noncopyable
{
// 在 get() 中,将 pStock.reset(new Stock(key)); 改为:
// pStock.reset(new Stock(key),
// boost::bind(&StockFactory::deleteStock, this, _1)); // ***
private:
    void deleteStock(Stock* stock) {
        if (stock) {
            MutexLockGuard lock(mutex_);
            stocks_.erase(stock->key());
        }
        delete stock; // sorry, I lied
}
// assuming StockFactory lives longer than all Stock's ...
// ...

警惕的读者可能已经发现问题,那就是我们把一个原始的 StockFactory this 指针保存在了 boost::function 里(*** 处),这会有线程安全问题。如果这个 Stock-Factory 先于 Stock 对象析构,那么会 core dump。

StockFactory::get() 把原始指针 this 保存到了 boost::function 中(*** 处),如果 StockFactory 的 生 命 期 比 Stock 短, 那 么 Stock 析 构 时 去 回 调 StockFactory::deleteStock 就会 core dump。似乎我们应该祭出惯用的 shared_ptr 大法来解决对象生命期问题,但是 StockFactory::get() 本身是个成员函数,如何获得一个指向当前对象的 shared_ptr 对象呢?

为了使用 shared_from_this(),StockFactory 不能是 stack object,必须是 heap object 且由 shared_ptr 管理其生命期,即:

shared_ptr<StockFactory> stockFactory(new StockFactory);

万事俱备,可以让 this 摇身一变,化为 shared_ptr 了。

// version 4
shared_ptr<Stock> StockFactory::get(const string& key)
{
// change
// pStock.reset(new Stock(key),
// boost::bind(&StockFactory::deleteStock, this, _1));
// to
    pStock.reset(new Stock(key),
    boost::bind(&StockFactory::deleteStock, shared_from_this(), _1));
// ...

这样一来,boost::function 里保存了一份 shared_ptr,可以保证调用 StockFactory::deleteStock 的时候那个 StockFactory 对象还活着。 注意一点,shared_from_this() 不能在构造函数里调用,因为在构造 StockFactory 的时候,它还没有被交给 shared_ptr 接管。

最后一个问题,StockFactory 的生命期似乎被意外延长了。

把 shared_ptr 绑(boost::bind)到 boost:function 里,那么回调的时候 StockFactory 对象始终存在,是安全的。这同时也延长了对象的生命期,使之不短于绑得的 boost:function 对象。

有时候我们需要“如果对象还活着,就调用它的成员函数,否则忽略之”的语意,就像 Observable::notifyObservers() 那样,我称之为“弱回调”。这也是可以实现的,利用 weak_ptr,我们可以把 weak_ptr 绑到 boost::function 里,这样对象的生命期就不会被延长。然后在回调的时候先尝试提升为 shared_ptr,如果提升成功,说明接受回调的对象还健在,那么就执行回调;如果提升失败,就不必劳神了。

class StockFactory : public boost::enable_shared_from_this<StockFactory> {
    public:
        shared_ptr<Stock> get(const string& key) {
            shared_ptr<Stock> pStock;
            MutexLockGuard lock(mutex_);
            weak_ptr<Stock>& wkStock = stocks_[key]; // 注意 wkStock 是引用
            pStock = wkStock.lock();
            
            if (!pStock) {
                pStock.reset(new Stock(key),
                boost::bind(&StockFactory::weakDeleteCallback,
                boost::weak_ptr<StockFactory>(shared_from_this()), _1));
        	 	// 上面必须强制把 shared_from_this() 转型为 weak_ptr,才不会延长生命期,
				// 因为 boost::bind 拷贝的是实参类型,不是形参类型
                wkStock = pStock;
            }
            return pStock; 
        }
  private:
	static void weakDeleteCallback(const boost::weak_ptr<StockFactory>& wkFactory, Stock* stock) {
					shared_ptr<StockFactory> factory(wkFactory.lock()); // 尝试提升
                    if (factory) // 如果 factory 还在,那就清理 stocks_ 
                    {
						factory->removeStock(stock);
                    }
					delete stock; // sorry, I lied
          }
};

shared_ptr的线程安全

  • 一个 shared_ptr 对象实体可被多个线程同时读取;
  • 两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;
  • 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

请注意,以上是 shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。 良好的使用shared_ptr的方式

void read()
{
	shared_ptr<Foo> localPtr;
	
    {
		MutexLockGuard lock(mutex);
		localPtr = globalPtr; // read globalPtr
    }
	// use localPtr since here,读写 localPtr 也无须加锁
	doit(localPtr);
}

写也要加锁:

void write()
{
    shared_ptr<Foo> newPtr(new Foo); // 注意,对象的创建在临界区之外
	{
		MutexLockGuard lock(mutex);
		globalPtr = newPtr; // write to globalPtr
	}
	// use newPtr since here,读写 newPtr 无须加锁
	doit(newPtr);
}

shared_ptr on void

class Request {
 std::shared_ptr<void> pData;
public:
 //Called by clients to set user-data
 void setUserData(const std::shared_ptr<void>& p) {
   pData = p;
 }
};

class Response {
 std::shared_ptr<void> pData; // Copied from Request
public:
 //Called by clients
 template<typename T>
 auto getUserData() {
  return std::static_pointer_cast<T>(pData);
 }
};
 
//Create a request and attach user data
Request r;
r.setUserData(std::make_shared<Context>(/*..*/)); // Set a context
// Send request asynchrounously.  

// Response handler callback
void onResponse(Response* pr) {
 auto pc = pr->getUserData<Context>();
 // Process response using context....
 // No explicit context delete here
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment