【1】为什么需要智能指针?
从本质上来说,智能指针的引入实际上是为了解决一个问题:为了有效准确地管理程序运行过程中动态申请的内存资源,以妥善地解决动态申请到的内存的拥有权问题。
在C++程序中,我们经常会出于这样那样的原因,需要从系统堆(heap)中动态申请内存,用于存放一些只有在程序运行期间才能确定其大小的数据对象。而在现实的计算机系统中,内存资源的总量是有限的,所以,有了申请,就得要有释放,只有那样,才能好还好借,为从heap中持续申请获得内存做好铺垫。如果,对于每一个动态内存申请点来说,我们都能够清晰地明确其释放点,那么,动态申请内存的释放似乎并不是一件值得为之特殊考虑的事情。但是,实际软件系统中的数据依赖关系往往非常复杂,很多时候,会复杂到不太容易确定一块动态申请到的内存到底到何时才真的可以允许释放掉。比如说,对于同一块动态申请到的内存,在程序中往往会有多个引用点,这种多指一的关系,就提出了额外的数据一致性的要求,只有在确保多个引用点都不会再访问这块动态内存的前提下,才可以安全地将其释放掉。再比如说,C++中的异常处理机制强化了程序员处理错误场景的能力,但是异常这种机制在本质上类似于goto语句的灵活性也为动态内存的释放增加了管理上的负担。当然,为了精准无误的手工释放动态申请到的内存,程序员可以仔细地对程序中的数据依赖关系作仔细地考量和设计,从而确保安全准确地手工释放动态内存。如果能够将“动态内存拥有权”这个问题的复杂性,从软件产品本身的复杂性中分离出来,由专门的人或团队来负责实现,实际上是非常有利于软件的模块化和复用性的。毕竟,从本质上来看,动态内存的拥有权和一款软件产品本身所要解决的目标问题在相当大程度上是正交的,将其分解开来,分而治之从软件工程学的角度来看实在是个不错的选择。
【2】智能指针怎样解决了“动态内存拥有权”问题?
概要地来说,智能指针通过增加一个封装类,让指向动态内存的指针在封装类的对象层面具备了值语义,从而解决了指针所指向的动态内存的拥有权问题。进一步深入探讨,我们发现动态内存指针之所以不满足值语义,是因为动态内存指针涉及到其指向的动态内存的拥有权的管理,而在将一个动态内存指针所指向的动态内存地址复制到目标指针的同时,指针所指向的动态内存的拥有权也被该目标指针共享,对同一片动态内存区域,存在两个引用点,这就带来了同步性的问题,对源指针和目标指针的操作不是完全正交的。
从本质上来说,给定一块动态内存区域,如果存在多个指向它的对象话,动态内存拥有权的同步问题是不可回避的。不过,我们是不是可以将这个拥有权的同步问题封装在有限的范围,从而在对象接口层面让客户端的代码获得值语义的使用体验呢?这正是智能指针的设计理念。
通常来说,智能指针会提供一个封装类,对C++中原生的指针进行封装,在封装类内部,通过一个计数器来协调指向同一块动态内存区域的多个智能指针之间的所有权关系。
而封装类会提供客户端代码通常会施加在原生指针上的绝大多数操作接口。这样,在保证跟原生指针相近的操作体验的同时,简化了动态内存的管理负担。
另外一个例子:
从本质上来说,智能指针的引入实际上是为了解决一个问题:为了有效准确地管理程序运行过程中动态申请的内存资源,以妥善地解决动态申请到的内存的拥有权问题。
在C++程序中,我们经常会出于这样那样的原因,需要从系统堆(heap)中动态申请内存,用于存放一些只有在程序运行期间才能确定其大小的数据对象。而在现实的计算机系统中,内存资源的总量是有限的,所以,有了申请,就得要有释放,只有那样,才能好还好借,为从heap中持续申请获得内存做好铺垫。如果,对于每一个动态内存申请点来说,我们都能够清晰地明确其释放点,那么,动态申请内存的释放似乎并不是一件值得为之特殊考虑的事情。但是,实际软件系统中的数据依赖关系往往非常复杂,很多时候,会复杂到不太容易确定一块动态申请到的内存到底到何时才真的可以允许释放掉。比如说,对于同一块动态申请到的内存,在程序中往往会有多个引用点,这种多指一的关系,就提出了额外的数据一致性的要求,只有在确保多个引用点都不会再访问这块动态内存的前提下,才可以安全地将其释放掉。再比如说,C++中的异常处理机制强化了程序员处理错误场景的能力,但是异常这种机制在本质上类似于goto语句的灵活性也为动态内存的释放增加了管理上的负担。当然,为了精准无误的手工释放动态申请到的内存,程序员可以仔细地对程序中的数据依赖关系作仔细地考量和设计,从而确保安全准确地手工释放动态内存。如果能够将“动态内存拥有权”这个问题的复杂性,从软件产品本身的复杂性中分离出来,由专门的人或团队来负责实现,实际上是非常有利于软件的模块化和复用性的。毕竟,从本质上来看,动态内存的拥有权和一款软件产品本身所要解决的目标问题在相当大程度上是正交的,将其分解开来,分而治之从软件工程学的角度来看实在是个不错的选择。
【2】智能指针怎样解决了“动态内存拥有权”问题?
概要地来说,智能指针通过增加一个封装类,让指向动态内存的指针在封装类的对象层面具备了值语义,从而解决了指针所指向的动态内存的拥有权问题。进一步深入探讨,我们发现动态内存指针之所以不满足值语义,是因为动态内存指针涉及到其指向的动态内存的拥有权的管理,而在将一个动态内存指针所指向的动态内存地址复制到目标指针的同时,指针所指向的动态内存的拥有权也被该目标指针共享,对同一片动态内存区域,存在两个引用点,这就带来了同步性的问题,对源指针和目标指针的操作不是完全正交的。
从本质上来说,给定一块动态内存区域,如果存在多个指向它的对象话,动态内存拥有权的同步问题是不可回避的。不过,我们是不是可以将这个拥有权的同步问题封装在有限的范围,从而在对象接口层面让客户端的代码获得值语义的使用体验呢?这正是智能指针的设计理念。
通常来说,智能指针会提供一个封装类,对C++中原生的指针进行封装,在封装类内部,通过一个计数器来协调指向同一块动态内存区域的多个智能指针之间的所有权关系。
而封装类会提供客户端代码通常会施加在原生指针上的绝大多数操作接口。这样,在保证跟原生指针相近的操作体验的同时,简化了动态内存的管理负担。
【3】一个简化的例子
假设一个class叫 CLargeObject,里面存有很多数据。
我们用一个inner class来把所有数据放在一起,叫CData。CData里面存有大量数据,例如一个数据库。
这里用最简单的模型来表示,假设只有一个整数int m_nVal。
CData里面需要包含另一个变量,叫作索引数目(reference count)。
它记录了总共有多少CLargeObject的object正在引用着当前的CData object。
示例代码如下:
#include<iostream>
using namespace std;
class CLargeObject
{
public:
CLargeObject(int nVal);
CLargeObject(const CLargeObject & ob);
~CLargeObject();
CLargeObject & operator = (const CLargeObject &ob);
void SetVal(int nNewVal);
int GetVal()const;
private:
struct Data
{
public:
Data(int nVal):m_nVal(nVal),m_nReferenceCount(1)
{}
private:
friend class CLargeObject;
Data *get_own_copy()
{
if(m_nReferenceCount == 1)
return this;
m_nReferenceCount--;
return new Data(m_nVal);
}
int m_nReferenceCount;
int m_nVal;
};
Data *m_pData;
};
CLargeObject::CLargeObject(int nVal)
{
m_pData = new Data(nVal);
}
CLargeObject::CLargeObject(const CLargeObject &ob)
{
ob.m_pData->m_nReferenceCount++;
m_pData = ob.m_pData;
}
CLargeObject::~CLargeObject()
{
if(--m_pData->m_nReferenceCount == 0)
delete m_pData;
}
CLargeObject & CLargeObject::operator =(const CLargeObject &ob)
{
ob.m_pData->m_nReferenceCount++;
if(--m_pData->m_nReferenceCount == 0)
delete m_pData;
m_pData = ob.m_pData;
return *this;
}
void CLargeObject::SetVal(int nNewVal)
{
m_pData = m_pData->get_own_copy();
m_pData->m_nVal = nNewVal;
}
int CLargeObject::GetVal() const
{
return m_pData->m_nVal;
}
void main()
{
CLargeObject obj1(10);
CLargeObject obj2(obj1);
obj2.SetVal(22);
cout<<obj1.GetVal()<<endl; //10
cout<<obj2.GetVal()<<endl; //22
}
另外一个例子:
#include<iostream>
using namespace std;
class U_Ptr
{
friend class HasPtr; //友元类
int *ip;
size_t use; //计数器
U_Ptr(int *p=NULL):ip(p),use(1)
{
}
~U_Ptr()
{
delete ip;
cout<<"U_Ptr"<<this<<endl;
ip = NULL;
}
};
class HasPtr
{
private:
U_Ptr *ptr;
int val;
public:
构造函数
HasPtr(int *ip = NULL, int i = 0):ptr(new U_Ptr(ip)),val(i)
{
cout<<"construct"<<this<<endl;
}
///拷贝构造函数
HasPtr(const HasPtr & orig):ptr(orig.ptr),val(orig.val)
{
cout<<"copy construct"<<this<<endl;
++ptr->use;
}
赋值函数
//赋值意味着左操作对象已经存在
//赋值分为自身赋值和非自身赋值
//自身赋值:尽量避免。若此例中自身赋值,分析如下:
//首先,各自计数均加1;其次,左操作对象计数减1,若为0,则delete;否则对应成员赋值。
//非自身赋值:一般必做判断if(this != &rhs)此例中分析如下:
//首先,有操作对象计数加1;其次,左操作对象计数减1,若为0,则delete;否则对应成员赋值。
HasPtr & operator =(const HasPtr &rhs)
{
cout<<"operator = "<<this<<endl;
++rhs.ptr->use; //右操作数的计数加1
if(--ptr->use == 0) //如果左操作数的引用计数减为0,则执行delete
{
delete ptr;
cout<<"operator = ptr->use == 0"<<" "<<this<<endl;
}
ptr = rhs.ptr;
val = rhs.val;
return *this;
}
//防止自身赋值
//如果是自身赋值,那么意味着引用计数应该不会改变,仅仅只是赋值
//如果非自身赋值,那么右操作数的计数加1。左操作数的计数减1,若减后为0,则执行delete.
///析构函数
~HasPtr()
{
if(--ptr->use == 0)
{
delete ptr;
cout<<"delete ptr"<<" "<<this<<endl;
}
cout<<"~ HasPtr()"<<this<<" "<<this->ptr->use<<endl;
ptr = NULL;
}
int *get_ptr() const
{
return ptr->ip;
}
int get_int() const
{
return val;
}
void set_ptr(int *p)
{
ptr->ip = p;
}
void set_int(int i)
{
val = i;
}
int get_ptr_val() const
{
return *(ptr->ip);
}
void set_ptr_val(int i)
{
*ptr->ip = i;
}
};
void main()
{
int *p = new int(1);
HasPtr obj1(p,100);
cout<<obj1.get_int()<<endl;
cout<<obj1.get_ptr()<<endl;
cout<<obj1.get_ptr_val()<<endl;
HasPtr obj2(obj1); //拷贝构造函数
cout<<obj2.get_int()<<endl;
cout<<obj2.get_ptr()<<endl;
cout<<obj2.get_ptr_val()<<endl;
HasPtr obj3 = obj1; //拷贝构造函数
cout<<obj3.get_int()<<endl;
cout<<obj3.get_ptr()<<endl;
cout<<obj3.get_ptr_val()<<endl;
HasPtr obj4;
obj4 = obj1; //赋值函数
cout<<obj4.get_int()<<endl;
cout<<obj4.get_ptr()<<endl;
cout<<obj4.get_ptr_val()<<endl;
obj4 = obj4; //自身赋值语句 检测赋值函数作用
int *s = new int(20);
cout<<s<<endl;
obj1.set_ptr(s);
cout<<obj1.get_ptr()<<endl;
cout<<obj2.get_ptr()<<endl;
cout<<obj3.get_ptr()<<endl;
obj1.set_int(99);
obj2.set_int(88);
obj3.set_int(77);
cout<<obj1.get_int()<<endl;
cout<<obj2.get_int()<<endl;
cout<<obj3.get_int()<<endl;
obj1.set_ptr_val(66);
obj1.set_ptr_val(55);
obj1.set_ptr_val(44);
cout<<obj1.get_ptr_val()<<endl;
cout<<obj2.get_ptr_val()<<endl;
cout<<obj3.get_ptr_val()<<endl;
}
/*
*运行结果:
construct003EFEF0
100
00724B48
1
copy construct003EFEE0
100
00724B48
1
copy construct003EFED0
100
00724B48
1
construct003EFEC0
operator = 003EFEC0
U_Ptr00724C20
operator = ptr->use == 0 003EFEC0
100
00724B48
1
operator = 003EFEC0
00724C20
00724C20
00724C20
00724C20
99
88
77
44
44
44
~ HasPtr()003EFEC0 3
~ HasPtr()003EFED0 2
~ HasPtr()003EFEE0 1
U_Ptr00724B88
delete ptr 003EFEF0
~ HasPtr()003EFEF0 4277075694
*/
转载:http://www.cnblogs.com/Braveliu/archive/2013/01/06/2848180.html