首页 > 代码库 > C++11之 unique_ptr

C++11之 unique_ptr

原文地址为:http://www.drdobbs.com/cpp/c11-uniqueptr/240002708

在C++11中加入了很多的新特性,unique_ptr一枝独秀,对于动态分配的内存对象,它简单有效。虽然它不是万能的,但是它做的已经够好了:利用简单的语法便可以管理动态分配的对象。


基本语法:

unique_ptr<T> 是一个模板类,你可以很简单地构造一个unique_ptr的对象,如下:

std::unique_ptr<foo> p( new foo(42) );

构造完成之后,你便可以像通过一般的指针一样来操作对象。比如operator*以及operator->操作符还是一样如你所预期的那样工作。


正如你可以像使用一般地指针那样使用unique_ptr类,最最重要的还是你可以unique_ptr会在超出作用域时自动的销毁该对象。你不必担心在作用域的某个出口忘记delete导致内存泄露,甚至在出现异常之后它也可以自动的销毁对象。


unique_ptr与容器

到目前为止,一切都是幸运的,根据标准C++的语法你同样可以实现上面的那些种种功能,实际上,auto_ptr这个不幸者(C++11已经将其废弃掉了)便是可以实现上述功能的,作为一个RAII的包裹器。

不行的是,auto_ptr并不能适当的工作,即使对于一些基本的操作,auto_ptr的表现也不尽人意。例如,如果你需要创建一个存放auto_ptr的容器,那么这将是一个大问题.


补充:关于对象与容器的关系,下面我自己写了一段简单的代码以作理解:

class A
{
public:
    A() {cout << "A ctor called..." << endl;}
    A(const A&) {cout <<"A copy ctor called..." << endl;}
};

int main(void)
{
    vector<A> vec;
    for(int i=0;i<5;++i)
    {
        cout << " i = " << i << endl;
        vec.push_back(A());  //构造该对象并且复制该对象。见下面代码的运行结果
    }
    return 0;
}

上述代码的执行结果是: 


可以看到,上述代码的执行结果中调用了拷贝构造函数。


[continue] 步入正题:

C++11加入了右值引用(rvalue reference) 和 move语意(move semantic) 来解决这些问题。幸运的是,经过修复,unique_ptr可以存储在容器中,即使容器被resize并且或者是被move都有正确的语意。并且当容器被销毁时,这些指针管理的资源也可以被正常的销毁。


唯一性与move语意:

unique这个词到底意味着什么呢?就如其字面意思一样,当你创建一个unique_ptr时,你就宣称这个指针就是独一份的,没有歧义的,就只有你可以拥有它,别人不可能也不会不经意的复制它。

比如,对于一个一般的指针,有如下代码:

foo *p = new foo("useful object");

make_use(p) ; // make_use函数的参数是一个对象指针

这里,我分配了一个对象并且有一个指针p指向它,当我在调用make_use函数的时,指针p会发生什么呢?make_use会为该指针做一份拷贝吗?在调用完毕之后会释放掉内存吗?或者说它就只是简单的借用一会儿该指针就原封不动的还回来,让调用者去释放空间呢?


上面的问题我们一个也无法回答, 是因为C++本身并没有对怎么使用指针这件事情作任何的约定,你只有通过查看自己的代码,查看自己的把内存以及文档来解决。


所幸的是,有了unique_ptr,这些问题都不是问题了,如果你传了一个指针给另外一个例程(权当函数理解了)。你不会对该指针做一份copy(因为它是unique的),即使你那样做,编译器也是不答应的。


指针的拥有者:

首先来一个简单的例子:创建一个unique_ptr,将其存放在一个容器中。作为一个unique_ptr的新手,你可能写出下面的代码:

std::unique_ptr<foo> q( new foo(42) );
v.push_back( q );

这似乎是合理的,但是这样做会让我进入一个灰色地带:谁是这个指针的拥有者,这个容器会在它生命周期的某个时刻释放该指针吗?或者是还得由创建者来自己释放它?


面对这些纠结,unique_ptr 禁止这样的代码。编译这样的代码将会导致编译错误。


Anyway,这里的问题就是我们只允许有该指针的一份拷贝。如果你想要将该对象交给另一个对象,就必须调用move函数,也就是说你必须放弃掉该对象的拥有权。

如:

v.push_back( std::move(q) );

执行完上述语句之后,q已经变成空的了,因为q已经放弃了该对象的拥有权,将拥有权交给了容器。


move语意可以用在任何你需要创建一个“右值引用”的地方。例如下面的代码:

return q; 

返回一个unique_ptr则不需要任何特殊的代码就可以完成。

还有,创建一个临时的对象给一个需要unique_ptr的函数也是不需要特殊处理的。如:

process( std::unique_ptr<foo>( new foo(41) ) );


Legacy Code: 老程序,其实也就是兼容性啦。

当你在使用unqiue_ptr的时候。你发现你现在需要的是一个底层的指针,那么有两种方式:

do_something( q.get() );          //retain ownership
do_something_else( q.release() ); //give up ownership

get函数是不会转交拥有权的。 因此在大多数情况下get 函数是不提倡使用的。因为你一旦将unique_ptr包裹的真正的指针释放给函数使用了,那么你就很难控制该函数到底会对这个指针做些什么操作。也就是说你必须对你的函数谨慎再谨慎,以保证该函数只是简单的借用一下该指针而已。

而release函数则是一个比较靠谱的方式了,当你向上述一样对指针q调用 release时,其实你就已经宣称说:该对象已经不归我管了,现在就是你的了。


当你的代码写的比较成熟的时候,这样的话就不会再频繁的出现了。

还有,当unique_ptr作为引用对象传递给函数的时候,如下:

void inc_baz( std::unique_ptr<foo> &p )
{
    p->baz++;
}

因为是传引用,所以你完全不必要担心该指针会被复制或者模糊了拥有者之类的事情了。


关于auto_ptr的使用,其实我们只需要在代码中多多的使用auto关键字来做类型推断,那么实际上我们在改写自己的代码来使用unqiue_ptr的时候,我们不需要改变更多的用户代码。


C++11之 unique_ptr