智能指针

前奏:

Garbage Collection 技术一直颇受注目,并且在 Java 中已经发展成熟,成为内
存管理的一大利器,但它在 C++ 语言中的发展却不顺利,C++ 为了追求运行速度,20 年来
态度坚决地将其排除在标准之外。

为了稍许平复因为没有 Garbage Collection 而引发的 C++ 程序员的怨气,C++
对 Smart Pointer 技术采取了不同的态度。

首先,了解一下智能指针,

该方法使用一个指针类来代表对资源的管理逻辑,并将指向资源的句柄(指针
或引用)通过构造函数传递给该类。当离开当前范围(scope)时,该对象的析构函数一定会
被调用,所以嵌在析构函数中的资源回收的代码也总是会被执行。这种方法的好处在于,由
于将资源回收的逻辑通过特定的类从原代码中剥离出来,自动正确地销毁动态分配的对象,
这会让思路变得更加清晰,同时确保内存不发生泄露。

它的一种通用实现技术是使用引用计数(Reference Count) 。引用计数智能指针,是一
种生命期受管的对象,其内部有一个引用计数器。当内部引用计数为零时,这些对象会自动
销毁自身的智能指针类。每次创建类的新对象时,会初始化指针并将引用计数置为 1 ;当对
象作为另一对象的副本而创建时,它会调用拷贝构造函数拷贝指针并增加与之相应的引用计
数 ;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数 ;如果引用计数
减至 0,则删除对象,并增加右操作数所指对象的引用计数 ;调用析构函数时,构造函数减
少引用计数,直到计数为 0,释放对象空间。

其次,auto_ptr包含在#include <memory>

auto_ptr 可以指向一个以 new 建立的对象,当 auto_ptr 的生命周期结束时,其所指向的
对象之资源也会被自动释放,且不必显式地调用 delete,而对象指针的操作依旧如故。例如:
class A
{
public:
A(){}
~A(){}
void Hello()

{
std::cout<<"Hello Smart Pointer";
}
};
int main()
{
std::auto_ptr<A> pA(new A());
pA->Hello();
return 0;
}
当然,也可以建立一个未指向任何对象的 auto_prt,例如:
std::auto_ptr<int> iPtr;
它就像空指针,未指向任何对象,所以也就不能进行操作,但是可以通过 get() 函数来
判断它是否指向对象的地址
if(iPtr.get() == 0) // 不指向任何对象
{
iPtr.reset(new int(2011)); // 指向一个对象
}
auto_ptr 还可以使用另一个 auto_ptr 来建立,但是需要十分小心的是,这会造成所有权
的转移,例如:
auto_ptr< string> sPtr1 (new string("Smart Pointer"));
auto_ptr< string> sPtr2 (sPtr1);
if( !sPtr1->empty() )
cout<<*sPtr1<< endl;
当使用 sPtr1 来建立 sPtr2 时,sPtr1 不再对所指向对象的资源释放负责,而是将接力
棒传递到了 sPtr2 的手里,sPtr1 丧失了使用 string 类成员函数的权利,所以在判断 sPtr1-
>empty() 时程序会崩溃。
auto_ptr 的资源维护动作是以 inline 的方式来完成的,在编译时代码会被扩展开来,所
以使用它并不会牺牲效率。虽然 auto_ptr 指针是一个 RAII (Resource Acquisition In Initialization)对象,能够给我们带来很多便利,
但是它的缺点同样不可小觑:

auto_ptr 对象不可作为 STL 容器的元素,所以二者带来的便利不能同时拥有。这一重
大缺陷让 STL 的忠实拥趸们愤怒不已。
auto_ptr 缺少对动态配置而来的数组的支持,如果用它来管理这些数组,结果是可怕
的、不可预期的。
auto_ptr 在被复制的时候会发生所有权转移

就在 2011 年的 9 月刚刚获得通过的 C++ 新标
准 C++ 11 中废弃了 auto_ptr 指针,取而代之的是两个新的指针类:shared_ptr 和 unique_ptr
shared_ptr 只是单纯的引用计数指针,unique_ptr 是用来取代 auto_ptr 的。unique_ptr 提供了
auto_ptr 的大部分特性,唯一的例外是 auto_ptr 的不安全、隐性的左值搬移 ;而 unique_ptr
可以存放在 C++0x 提出的那些能察觉搬移动作的容器之中。

在 Boost 中的智能指针共有五种 :scoped_ptr、scoped_array、shared_ptr、shared_array、
weak_ptr,其中最有用的就是 shared_ptr,它采取了引用计数,并且是线程安全的,同时支
持扩展,推荐在大多数情况下使用。

boost::shared_ptr 支持 STL 容器:
typedef boost::shared_ptr<string> CStringPtr;
std::vector< CStringPtr > strVec;
strVec.push_back( CStringPtr(new string("Hello")) );

当 vector 被销毁时,其元素 — 智能指针对象才会被销毁,除非这个对象被其他的智能
指针引用,如下面的代码片段所示:
typedef boost::shared_ptr<string> CStringPtr;
std::vector< CStringPtr > strVec;
strVec.push_back( CStringPtr(new string("Hello")) );
strVec.push_back( CStringPtr(new string("Smart")) );
strVec.push_back( CStringPtr(new string("Pointer")) );
CStringPtr strPtr = strVec[0];
strVec.clear(); //strVec 清空,但是保留了 strPtr 引用的 strVec[0]
cout<<*strPtr<<endl; // strVec[0] 依然有效

Boost 智能指针同样支持数组,boost::scoped_array 和 boost::shared_array 对象指向的是
动态配置的数组。
Boost 的智能指针虽然增强了安全性,处理了潜在的危险,但是我们在使用时还是应该
遵守一定的规则,以确保代码更加鲁棒。

规则 1:Smart_ptr<T> 不同于 T*
Smart_ptr<T> 的真实身份其实是一个对象,一个管理动态配置对象的对象,而 T* 是指
向 T 类型对象的一个指针,所以不能盲目地将一个 T* 和一个智能指针类型 Smart_ptr<T> 相
互转换。
在创建一个智能指针的时候需要明确写出 Smart_ptr<T> tPtr<new T>。
禁止将 T* 赋值给一个智能指针。

不能采用 tPtr = NULL 的方式将 tPtr 置空,应该使用智能指针类的成员函数。

规则 2:不要使用临时的 share_ptr 对象

class A;
bool IsAllReady();
void ProcessObject(boost::shared_ptr< A> pA, bool isReady);
ProcessObject(boost::shared_ptr(new A), IsAllReady());
调用 ProcessObject 函数之前,C++ 编译器必须完成三件事:
(1) 执行 "new A"。
(2) 调用 boost::shared_ptr 的构造函数。
(3) 调用函数 IsAllReady()。
因为函数参数求值顺序的不确定性,如果调用 IsAllReady() 发生在另外两个过程中间,
而它又正好出现了异常,那么 new A 得到的内存返回的指针就会丢失,进而发生内存泄露,
因为返回的指针没有被存入我们期望能阻止资源泄漏的 boost::shared_ptr 上。避免出现这种
问题的方式就是不要使用临时的 share_ptr 对象,改用一个局部变量来实现在一个独立的语
句中将通过 new 创建出来的对象存入智能指针中:

boost::shared_ptr<A> pA(new A)
ProcessObject(pA, IsAllReady());
如果疏忽了这一点,当异常发生时,可能会引起微妙的资源泄漏。

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <string.h>
 5 #include <vector>
 6 #include <time.h>
 7 #include <assert.h>
 8 #include <memory>
 9 #include <signal.h>
10 using namespace std;
11 
12 int main(){
13     shared_ptr<int> p(new int(4));
14     shared_ptr<int> p2 = make_shared<int>(4);
15     cout << *p << " ";
16     unique_ptr<int> p3(new int(4));
17     cout << *p3 << " ";
18     return 0;
19 }

 

boost库中还有一个scoped_ptr

首先,智能指针采用RAII机制,通过对象来管理指针,构造对象时,完成资源的初始化;析构对象时,对资源进行清理及汕尾.

  auto_ptr,通过转移管理权来完成对象的拷贝与赋值,在实际开发中并不实用.

  回顾完智能指针的背景及auto_ptr的特性之后,本文来介绍scoped_ptr的实现原理及特性.

  scoped_ptr与auto_ptr类似,但最大的区别就是它不能转让管理权.也就是说,scoped_ptr禁止用户进行拷贝与赋值.

  诶?当我了解到这里的时候,那么问题来了:scoped_ptr是通过什么方法来做到禁止用户进行拷贝与赋值呢?

  换句话说:C++中有什么方法可以禁止一个类进行拷贝构造和赋值呢?

  那么我第一时间想到的是:既然禁止你进行拷贝和赋值,那我不写相应函数不就行了?

  在C++中,如果你不定义拷贝构造函数/赋值运算符重载函数的话,当你在调用时,系统会自动默认生成相应的函数.

  但,系统默认生成的函数,完成的仅仅是值的拷贝,即浅拷贝!(当然,深浅拷贝的问题,就不在这里多说了,以后再谈.)

  也就是说,这种方法绝对没办法禁止一个类进行拷贝构造与赋值.

  此时,我突然灵光一现:想起了《Effective C++》中某条款提到了这个,其实要做的很简单:

  我们只需把拷贝构造函数与赋值运算符重载的访问限定符设置为private,并且只给出其声明,就像这样:

class ScopedPtr{
private:
    ScopedPtr(const ScopedPtr& sp);
    ScopedPtr& operator(const ScopedPtr& sp);  
};

 

posted @ 2015-09-13 15:46  PKICA  阅读(458)  评论(0编辑  收藏  举报