C++11教程1 智能指针——unique_ptr

     在vs2013里,C++智能指针共有四种,分别是unique_ptrshared_ptrweak_ptr和一种用在COM编程里的智能指针CComPtr

    若要使用前三种智能指针,则需要包含头文件,同时不要忘了使用std命名空间。

      智能指针完全符合RAII的设计理念,因此我们并不需要像过去一样显示的去释放指针所指向的资源,智能指针会自动为我们管理资源并适时的释放资源,并且是异常安全的。所以在现代C++编程中,你若还在使用delete来释放内存,那么你就out了!快回到正途上来吧!

      当然,对于智能指针的使用,是建立在对其理解的基础上的,比如unique_ptr的不可复制性,让我们不能直接将其放到STL容器中,而在使用shared_ptr时,要注意按值传递及按引用传递的不同情况。关于这些问题,我慢慢会在教程里面讨论。

     OK,第一步,我们从unique_ptr说起。

吃独食的unique_ptr

    unique_ptr就像他的名字一样,他不允许其他人拷贝由他管理的原生指针。即unique_ptr一旦被赋予管理一个原生指针,那你就休息将该原生指针拷贝出去,你只能通过该unique_ptr来操作其所管理的原生指针。

    没错,彪悍的人生就是不需要解释。这就意味着,不能够将unique_ptr拷贝到另一个unique_ptr、在函数中不能够按值传递unique_ptr、不能够将他传递给需要拷贝副本的STL算法使用(如我们常用的STL容器mapvector)。

    当然,在实际的编程,为了提高效率和节约资源,在函数传递过程中我们都是按照引用传递的(你可以加上const来限制函数对被引用对象的更改)。但是,不能够将unique_ptr放到容器里,是不是很蛋疼?比如我们想用一个vector来管理unique_ptr,即用vector来管理资源,那下面的代码岂不是就不行了?

// 假设ConnectInfo 是你自定义的类,你需//要在每个客户端连接时,动态创建一个Con//nectInfo实例,并用它来存储客户端信息
class ConnectInfo
{
public:
ConnectInfo(int age):m_age(age){}
int m_age;
};
int main()
{
unique_ptr ptrA(new ConnectInfo(11));
vector< unique_ptr > connectInfos;
connectInfos.push_back(ptrA);
wprintf(L“Client age is %d\n”, connectInfos[0]->m_age);

}

   编译,果然不过,因为vector实际上使用的是原值的副本,而unique_ptr又是禁止拷贝的,因此编译器会报错。

unique_ptr所有权的转移

    其实,我们还是有办法利用容器存储的。

    我们将上面的代码稍微改写一下

class ConnectInfo
{
public:
ConnectInfo(int age):m_age(age){}
int m_age;
};
int main()
{
vector< unique_ptr > connectInfos;
connectInfos.push_back(unique_ptr (new ConnectInfo(11)));
wprintf(L“Client age is %d\n”, connectInfos[0]->m_age);

}

    编译,运行,成功!

    咦?怎么回事,怎么成功并正常显示了呢?难道vector不会拷贝对象副本了?我可以负责任的告诉你,vector的实现机制并没有改变。

    再看看更神奇的,我们在main中再修改代码,如下

int main()
{
unique_ptr ptrA(new ConnectInfo(11));
vector< unique_ptr > connectInfos;
connectInfos.push_back(std::move(ptrA));
wprintf(L“Client age is %d\n”, connectInfos[0]->m_age);

}

    编译,又成功了!怎么回事?这和之前的代码只多了std::move

    在成功的第一个例子中,我们是创建了一个匿名临时变量传入到vector中,而在第二个例子中,我们加上了C++11新的关键字std::move

    其实,这两个成功的例子的原理是一样的,即,我们利用了C++11的新特性,移动语义

    前面我们说过,unique_ptr不能够被拷贝,其核心原因还是因为unique_ptr所保存的原生指针不能够被多个unique_ptr同时管理。那么如果这么一种情况,unique_ptr A先保存了原生指针P,而这时候unique_ptr B也要保存P,同时,A让出其保存权。(即P的所有权从A转让到了B上,A不在和P有任何关系,此时P仍然是被一个唯一unique_ptr B管理的)。

    其转让关系如下图。

    

    好,这种转让的情况,unique_ptr是允许的。MSDN原话是这样说的

“This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it.”

    在成功例子1中,我们创建了一个无名临时变量放入vector中,vector会根据该临时变量为模板,调用拷贝构造函数创建一个副本放入vector中(这种情况是不会成功的,因为unique_ptr不允许对其管理的原生指针的拷贝)。但在C++11中,既然你传入的是一个临时变量,那么我偏偏不调用拷贝构造函数,而是会调用一种新的构造函数——“移动构造函数”。

   关于移动语义以及移动构造函数,扯多了就变成另一篇文章了。大家可参考:

   http://i.msdn.microsoft.com/dynimg/IC684645.png

   

    关于移动构造函数,我们只需要知道,对于传入“拷贝(移动)”构造函数的参数是临时变量时,对临时变量的指针类型成员,在“拷贝(移动)”构造时,他不会老实的去拷贝临时变量指针成员的内容,而是来了一个偷天换日:将自己的指针类型变量指向临时变量的指针所指内存,同时,将临时变量的指针置为空。这样,就避免了拷贝的发生,正是没有了拷贝的发生,我们的unique_ptr才顺利的存入了vector中。

    下面是unique_ptr的移动构造函数,若加断点的话,在vectorpush_back操作中,会进入到移动构造函数中。

    

unique_ptr(unique_ptr&& _Right)  // 注意,移动构造函数参数类型这里是unique_ptr&& 而非拷贝构造的nique_ptr&
		: _Mybase(_Right.release(),
			_STD forward<_Dx>(_Right.get_deleter()))
		{	// construct by moving _Right
		}
在__Right.release()中,
pointer release()
		{	// yield ownership of pointer
		pointer _Ans = this->_Myptr;
		this->_Myptr = pointer();
		return (_Ans);
		}
_Right让出所有权,同时返回原值,外层的unique_ptr获取到该值。

     移动构造函数和拷贝构造函数的触发时机类似,区别在于移动构造函数的触发时机在于传入的“构造模板对象”是临时变量,而拷贝构造函数的“构造模板对象”为非临时变量。

    同理,对于第二个成功的例子,我们只是将非临时变量利用std::move强制转换为了临时变量。而临时变量会在其语句结束后自动失效的。比如,我们下面的代码就会崩溃掉。

    

unique_ptr ptrA(new ConnectInfo(11));
vector< Unique_ptr > connectInfos;
wprintf(L“Client age is %d\n”, ptrA->m_age);   // OK
connectInfos.push_back(std::move(ptrA));      	 // 强转ptrA为临时变量,执行完该语句后自动失效
wprintf(L“Client age is %d\n”, ptrA->m_age);   // error!! ptrA为临时变量,已在上一句失效

Unique_ptr总结

    好了,关于unique_ptr就介绍到这里了。其实在编程的大多数环境下,我们用unique_ptr对原生指针进行管理,就足够了,而且由于unique_ptr的所有权唯一性,他不会像shared_ptr那样可能会照成引用计数混乱导致潜在的问题。所以我们可以试着多使用unique_ptr

   对于unique_ptr的使用方法,本文可能并没有介绍多少,而是将重点放在了一些细节上,之后的教程还是会延续这种风格。其实具体的用法,大家自行百度一下就好啦~

   下面是总结以及补充:

  • Unique_ptr是一种智能指针,自动释放其管理资源,但是不能被拷贝(即不能用拷贝构造函数,不能按值传递,不能够用于使用拷贝值得STL中)
  • 我们可以使用移动语义来巧妙的打破上面约束
  • 使用make_unique来异常安全的创建unique_ptr对象
  • 使用unique_ptrget方法可以获得其保存的原生指针
  • 使用正确的形式创建unique_ptr数组

    // Create a unique_ptr to an array of 5 Animals
        unique_ptr p3 = make_unique(5);

     

    或干脆 

    // Create a unique_ptr to an array of 5 Animals
    auto p3 = unique_ptr p3 = make_unique(5);
    

    千万不要写成

    // Create a unique_ptr to an array of 5 Animals
        unique_ptr p3 = make_unique(5);  // 这里p3是一个unique_ptr对象,而非数组!创建出来的5个Animal元素,其实你只管理了一个(囧)







你可能感兴趣的:(C++,11)