您的位置:首页 > 编程语言 > C语言/C++

我的C++实践(10):智能指针

2016-07-29 00:00 495 查看
1、独占型智能指针: HolderPtr<T>。智能指针对象独占被管理对象的所有权,不允许对智能指针进行拷贝和赋值。可以提供一个release()函数来让出所有权。

//holderptr.hpp:独占型智能指针,不允许复制和赋值的智能指针
#ifndef HOLDERPTR_HPP
#define HOLDERPTR_HPP
template<typename T>
class HolderPtr{
private:
T* ptr; //指向它所持有的对象(前提是该对象存在)
public:
HolderPtr():ptr(0){ //缺省构造函数:指向空对象
}
explicit HolderPtr(T* p):ptr(p){ //针对内建指针的构造函数,禁止隐式转型
}
~HolderPtr(){
if(ptr)
delete ptr;
}
HolderPtr<T>& operator=(T* p){ //针对内建指针的赋值运算符
if(ptr) delete ptr; //删除原来持有的对象
ptr=p;
return *this;
}
T& operator*(){ //指针运算符
return *ptr;
}
T& operator*() const{ //const版本
return *ptr;
}
T* operator->(){ //箭头运算符
return ptr;
}
T* operator->() const{ //const版本
return ptr;  //在返回类型中,T本身可以是const类型,故用T*而不用T const*
}
T* get() const{ //获取里面的内建指针
return ptr;
}
T* release(){ //释放对持有对象的所有权
T* ret=ptr;
ptr=0; //这样本holder不再持有对象,即使析构也无影响(ptr变成NULL)
return ret; //返回新的持有对象的指针
}
void exchange_with(HolderPtr<T>& h){ //与另一个holder交换对象所有权
swap(ptr,h.ptr);
}
void exchange_with(T*& p){ //与其他的内建指针交换对象所有权
swap(ptr,p);
}
void reset(T* p=0){ //重置为一个新的指针
if(ptr!=p){
delete ptr;
ptr=p;
}
}
private:
//禁止对智能指针的拷贝和赋值
HolderPtr(HolderPtr<T> const&);
HolderPtr<T>& operator=(HolderPtr<T> const&);
void swap(T*& lhs,T*& rhs){ //指针交换函数
T* tmp=lhs;
lhs=rhs;
rhs=tmp;
}
};
#endif


2、转移型智能指针: AutoPtr<T>。允许对智能指针进行拷贝或赋值,但拷贝或赋值后对象所有权就会自动转移到其他的智能指针,原来的智能指针指向NULL。例如标准库中的std::auto_ptr就属于这一类智能指针。

//autoptr.hpp:转移型智能指针
#ifndef AUTOPTR_HPP
#define AUTOPTR_HPP
template<typename T>
class AutoPtr{
private:
T *pointee; //指向持有的对象
template<typename U>
friend class AutoPtr;
public:
explicit AutoPtr(T* p=0):pointee(p){
}
AutoPtr(AutoPtr<T>& rhs):pointee(rhs.release()){ //默认的拷贝构造函数
//参数不能为const的,因为转移对象的所有权需要修改指针成员的值
}
//针对其他兼容类型的拷贝构造函数
template<typename U>
AutoPtr(AutoPtr<U>& rhs):pointee(rhs.release()){
}
AutoPtr<T>& operator=(AutoPtr<T>& rhs){ //默认的赋值运算符
if(this!=&rhs) reset(rhs.release()); //转移对象的所有权
return *this;
}
//针对其他兼容类型的赋值
template<typename U>
AutoPtr<T>& operator=(AutoPtr<U>& rhs){
if(this->pointee!=rhs.pointee) reset(rhs.release());
return *this;
}

AutoPtr<T>& operator=(T* p){ //针对内建指针的赋值运算符
if(pointee) delete pointee; //删除原来持有的对象
pointee=p;
return *this;
}

~AutoPtr(){
if(pointee) delete pointee;
}
T& operator*() const{ //重载解引用运算符
return *pointee;
}
T& operator*(){ //非const版本
return *pointee;
}
T* operator->() const{ //重载箭头操作符
return pointee;
}
T* operator->(){ //非const版本
return pointee;
}

T* get() const{
return pointee;
}
T* release(){ //释放对象的所有权
T* old=pointee;
pointee=0;
return old;
}
void reset(T* p=0){ //重置为一个新指针
if(pointee!=p){
delete pointee;
pointee=p;
}
}
};
#endif


注意在拷贝和赋值时,函数的参数不能加const修饰符,因为转移对象的所有权需要修改参数的指针成员的值。另外要注意针对其他兼容类型的拷贝构造函数和赋值操作符使用了成员函数模板,它并不会覆盖默认的拷贝构造函数和赋值运算,因此我们还要显示提供默认的拷贝构造函数和赋值操作符,因为它们都是深拷贝和深赋值。
3、共享型智能指针: CountingPtr<T,CP,OP>。多个智能指针可以指向一个对象。这要对共享的指针个数进行计数,当计数器变为0时,就释放指向的对象。有多种计数策略(比如侵入式和非侵入式)和对象释放策略(比如对普通对象和数组对象释放策略不一样,有时可能必须要用free()来释放内存等),因此通常可以把计数功能和对象释放功能抽离出来设计成独立的计数policy和对象释放policy,通过模板参数传递给智能指针。我们这里把policy设计成具有成员函数模板的普通类,而不是模板,这样传递policy时就不需要模板模板参数,用普通的类型参数就可以了。
非侵入式计数器:计数器并不存储在所指向的对象内部,而是用独立的空间来存储和管理计数器。由于计数器对象比较小,又要被多个共享型智能指针所共享,为了提高性能,通常设计一个独立的分配器来分配大小固定的计数器对象。计数器policy类的操作包括分配、释放、加1、减1、判0等,设计成成员函数模板,形参为模板参数的指针类型,表示要计数的指针。代码如下:

//simpleallocator.hpp:简单的计数器内存分配器,这里只分配和管理size_t大小的内存空间
#ifndef SIMPLE_ALLOCATOR_HPP
#define SIMPLE_ALLOCATOR_HPP
#include <cstddef>
size_t* alloc_counter(){ //分配size_t大小的内存
return ::new size_t;
}
void dealloc_counter(size_t* ptr){ //释放内存
::delete ptr;
}
#endif


//simplerefcount.hpp:简单的非侵入式计数器
#ifndef SIMPLE_REFCOUNT_HPP
#define SIMPLE_REFCOUNT_HPP
#include <cstddef>  //用到size_t的定义
#include "simpleallocator.hpp"
class SimpleReferenceCount {
private:
size_t* counter; // 分配的计数器
public:
SimpleReferenceCount () {
counter = NULL;
}
// 缺省的拷贝构造函数和赋值运算符都是允许的,
// 因为它们只是拷贝这个共享计数器

template<typename T> void init (T*) { //分配计数器,并初始化为1
counter = alloc_counter();        //形参T*表示要计数的指针
*counter = 1;
}
template<typename T> void dispose (T*) { //释放计数器
dealloc_counter(counter);
}
template<typename T> void increment (T*) { //计数值加1
++*counter;
}
template<typename T> void decrement (T*) { //计数值减1
--*counter;
}
template<typename T> bool is_zero (T*) { //检查计数值是否为0
return *counter == 0;
}
};
#endif


侵入式计数器:将计数器放到被管理对象本身的类型中,是这个类型的一个普通成员变量,因此无需独立的分配器。侵入式计数器一般专用于管理某一种具体类型。计数器类应设计成模板,用一个模板参数来传递用作为计数器的成员变量指针。代码如下:

//memberrefcount.hpp:侵入式计数器
#ifndef MEMBER_REFCOUNT_HPP
#define MEMBER_REFCOUNT_HPP
template<typename ObjectT,        // 计数器所在对象(即被智能指针管理的对象)的类型
typename CountT,         // 计数器的类型
CountT ObjectT::* CountP> // 计数器所在位置(目标类型的成员指针)
class MemberReferenceCount{
public:
// 缺省构造函数和析构函数都是允许的
void init (ObjectT* object) { //对象的计数器的值初始化为1
object->*CountP = 1;
}
void dispose (ObjectT*) { //释放计数器,ObjectT*是要计数的对象指针
//并不需要执行任何操作
//因为计数器是目标类型的一个普通成员变量
//会随着目标对象的析构而自动释放
}
void increment (ObjectT* object) { //计数值加1
++(object->*CountP); //->*的优先级低于++
}
void decrement (ObjectT* object) { //计数值减1
--(object->*CountP);
}
bool is_zero (ObjectT* object) { //检查计数值是否为0
return object->*CountP == 0;
}
};
#endif


对象释放器:对普通对象用delete,对数组对象用delete[],有时我们可能必须要使用其他的方式来释放对象,比如用C函数free()。我们把它们设计不同的policy类。如下:

//objpolicies.hpp:对象释放器,根据对象的不同的类型,有不同的释放策略
#ifndef OBJPOLICIES_HPP
#define OBJPOLICIES_HPP
class StandardObjectPolicy{ //普通型对象的释放
public:
template<typename T> void dispose (T* object){
delete object;
}
};
class StandardArrayPolicy { //数组对象的释放
public:
template<typename T> void dispose (T* array){
delete[] array;
}
};
//其他对象释放策略,比如直接用free()等
//...
#endif


使用了计数policy和对象释放policy的共享型智能指针实现如下:

//countingptr.hpp:共享型智能指针,当对象的引用计数器变为0时,就删除指向的对象
#ifndef COUNTINGPTR_HPP
#define COUNTINGPTR_HPP
#include "simplerefcount.hpp"
#include "objpolicies.hpp"
template<typename T,
typename CounterPolicy = SimpleReferenceCount,
typename ObjectPolicy = StandardObjectPolicy>
class CountingPtr : private CounterPolicy, private ObjectPolicy { //模板参数作为基类
private:
typedef CounterPolicy CP;  //定义简单的别名:计数器的类型
typedef ObjectPolicy  OP;  //对象释放器的类型

T* object_pointed_to;      //所引用的对象(如果没有则为NULL)

template<typename T2,typename CP2,typename OP2>
friend class CountingPtr; //用于从兼容类型进行拷贝,要访问兼容类型的私有成员
/*
class BoolSupport{ //私有嵌套类,用于支持智能指针转型为成员指针,
//从而可直接隐式转型为bool类型
int dummy;
};
*/
void init (T* p) { // 用普通指针进行初始化
if (p != NULL) {
CounterPolicy::init(p);
}
this->object_pointed_to = p;
}

//拷贝用的成员函数模板:拷贝指针并且增加被拷贝指针的计数值
template<typename S>
void attach (CountingPtr<S,CP,OP> const& cp) {
//拷贝内建的指针
this->object_pointed_to = cp.object_pointed_to;
if (cp.object_pointed_to != NULL) {
//增加被拷贝的智能指针的引用计数
CP::increment(cp.object_pointed_to);
}
}

void detach() { // 减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
if (this->object_pointed_to != NULL) { //必须要检查是否为空
//减少本类的指针引用计数
CounterPolicy::decrement(this->object_pointed_to);
if (CounterPolicy::is_zero(this->object_pointed_to)) {
CounterPolicy::dispose(this->object_pointed_to); //使用计数器来释放计数器
ObjectPolicy::dispose(this->object_pointed_to);  //使用对象释放器来释放对象
}
}
}

public:
CountingPtr() { //缺省构造函数
this->object_pointed_to = NULL;
}

explicit CountingPtr(T* p) { //针对内建指针的构造函数
this->init(p); //使用普通指针初始化
}

template<typename U>
explicit CountingPtr(U* p){ //针对派生类指针的构造函数模板
this->init(p); //使用派生类指针初始化
}

CountingPtr(CountingPtr<T,CP,OP> const& cp) //缺省拷贝构造函数
: CP((CP const&)cp),      //拷贝基类部分的计数策略和对象释放策略
OP((OP const&)cp) {
this->attach(cp);  //复制指针并增加被拷贝的智能指针计数值
}

//对兼容类型的拷贝构造函数模板,支持了隐式转型,
//当T是void类型,或者是S的基类时,可从兼容类型转型为本类型
template<typename S>
CountingPtr(CountingPtr<S,CP,OP> const& cp)
:CP((CP const&)cp),
OP((OP const&)cp),
object_pointed_to(cp.object_pointed_to){
if(cp.object_pointed_to!=NULL)
//增加被拷贝的智能指针计数值
CP::increment(cp.object_pointed_to);
}

CountingPtr<T,CP,OP>&
operator= (CountingPtr<T,CP,OP> const& cp) { //缺省的赋值运算符
if (this->object_pointed_to != cp.object_pointed_to) {
this->detach();  //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
CP::operator=((CP const&)cp);  //对基类部分中的计数策略和对象释放策略进行赋值
OP::operator=((OP const&)cp);
this->attach(cp);  //拷贝指针并且增加被赋值的智能指针计数值
}
return *this;
}

template<typename S>
CountingPtr<T,CP,OP>&
operator= (CountingPtr<S,CP,OP> const& cp) { //对兼容类型的赋值运算符模板
if (this->object_pointed_to != cp.object_pointed_to) {
this->detach();  //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
CP::operator=((CP const&)cp);  //对基类部分中的计数策略和对象释放策略进行赋值
OP::operator=((OP const&)cp);
this->attach(cp);  //拷贝指针并且增加被赋值的智能指针的引用计数值
}
return *this;
}

CountingPtr<T,CP,OP>& operator=(T* p) { //针对内建指针的赋值运算符
if(p != this->object_pointed_to){ //计数指针不能指向*p
this->detach();  //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
this->init(p);   //用一个普通指针进行初始化
}
return *this;
}

template<typename U>
CountingPtr<T,CP,OP>& operator=(U* p){ //针对派生类指针的赋值运算符模板
if(p != this->object_pointed_to){ //计数指针不能指向*p
this->detach();  //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
this->init(p);   //用一个派生类指针进行初始化
}
return *this;
}

~CountingPtr() { //析构函数
this->detach();  //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
}

T* operator->(){ //重载箭头运算符
return this->object_pointed_to;
}

T* operator->() const { //const版本
return this->object_pointed_to;
}

T& operator*(){ //重载解引用运算符
return *this->object_pointed_to;
}

T& operator*() const { //const版本
return *this->object_pointed_to;
}
/*
//到成员指针的转型,从而可以直接隐式转型为bool类型
operator BoolSupport::*() const{
return this->object_pointed_to ? (&BoolSupport::dummy) : (BoolSupport::*)0;
}
*/
operator bool() const{ //直接的bool转型,会有一些副作用(比如导致两个智能指针可以相加)
return this->object_pointed_to!=(T*)0;
}
//比较运算符:与内建指针的直接比较
friend bool operator==(CountingPtr<T,CP,OP> const& cp,T const* p){
return cp==p;
}

friend bool operator==(T const* p,CountingPtr<T,CP,OP> const& cp){
return p==cp;
}

//其他的一些比较运算符
//...
};
//两个智能指针之间的比较:用内联函数
template<typename T1,typename T2,
typename CP,typename OP>
inline bool operator==(CountingPtr<T1,CP,OP> const& cp1,
CountingPtr<T2,CP,OP> const& cp2){
return cp1.operator->()==cp2.operator->();
}
#endif


有几点要注意:
(1)在detach()中,在减少计数值之前必须要检查智能指针是否为空,因为空指针并没有可关联的计数器。
(2)CountingPtr<T,CP,OP>直接继承了两种policy类,这样当policy类为空类时,就可以执行空基类优化。比如前面的侵入式计数器MemberReferenceCount就是一个空类,在CountingPtr中使用它时可以被优化掉,但非侵入式计数器SimpleReferenceCount不是空类(有一个指针成员),不能被优化。而对象释放器均为空类。可见一般非侵入式计数器会增加智能指针的大小(当然我们可以使用压缩存储技术来优化,这需要改变该policy类的设计),侵入式计数器则会增加被管理的对象的大小。
(3)计数器类中,函数模板的形参表示要进行计数的指针,但函数内部却并不需要用到这个指针,因为它只需要改变计数值即可。
(4)智能指针的常数性:与内建指针相比,X const*(即const X*)与CountingPtr<X const>对应,表示所指对象为常数。X* const与CountingPtr<X> const对应,表示指针本身为常数。因此在->和*重载中,const版本的返回类型是T*或T&,而不是T const*或T const&,因为T本身可以是const类型。
(5)智能指针的转型:内建指针可以隐式转型为基类指针、void*类型或bool类型。对智能指针,我们可以添加转型成员函数,以允许CountingPtr<T>转型为bool类型。我们也可以转型为CountingPtr<B>(通过单参数的拷贝构造函数完成),其中B为void类型或者为T的基类,使用这种转型时就不能用侵入式计数器,因为void类型没有计数器,且基类的计数器与子类的计数器通常也不同(各有各的计数器)。注意我们并不允许智能指针直接转型为内建指针,因为我们设计智能指针就是为了避免使用内建指针,而且如果允许的话,对智能指针进行运算(如cp
,cp2-cp1),将很难确定其结果。对于bool转型,可直接提供operator bool()运算符,但这会有一定的副作用,比如导致两个智能指针可以相加了。我们可以换一种解决方案,提供一个到成员指针的转型运算符(这需要增加一个私有的嵌套类,注意嵌套类并不会增加智能指针CountingPtr的大小),当转型到成员指针后,可以自动地隐式转型为bool类型。注意成员指针与内建指针不同,并不能对成员指针进行算术运算,因此没有副作用。但是,我在gcc上测试出这种方案不能通过(代码中的这部分我注释掉了),有可能是因为gcc并不支持到成员指针的转型运算符。
(6)智能指针的比较:内建指针支持相等比较(==,!=)和排序比较(,<,<=等)。对智能指针,可以添加相应的比较运算符函数。要注意有智能指针和内建指针的直接比较,也有两个智能指针之间的比较。对于两个智能指针之间的比较,我们把它设计成了一个独立的全局函数模板(在类外面),允许兼容类型的智能指针进行比较。由于是全局的非成员函数,因此要显式加inline表示内联,以提高性能。
4、测试代码。 当智能指针使用侵入式计数器时,由于计数器是在目标类型对象的内部,用于专门对这个类型的对象进行计数。因此为了使用方便,我们通常把智能指针组合到目标类型中,成为它的一个类型成员(这并没有增加目标类型的大小),这样通过这个类型成员就可以方便的定义我们需要的智能指针。

//smartptrtest.cpp:对各种智能指针的测试
#include "holderptr.hpp"
#include "autoptr.hpp"
#include "countingptr.hpp"
#include "memberrefcount.hpp"
#include <iostream>
class Something{ //测试类
public:
void perform() const{
}
};
class Derived: public Something{ //测试类
};
class MyClass{ //使用侵入式计数器的测试类
public:
size_t ref_count; //计数器
public:
typedef CountingPtr<MyClass,
MemberReferenceCount<MyClass,size_t,&MyClass::ref_count> >
SmartPtr; //管理本对象的智能指针类型

void perform() const{
}
size_t getCounter(){
return ref_count;
}
//...
};
void read_something(Something* x){ //测试函数
}
Something* load_something(){ //测试独占型指针的获取和转移
HolderPtr<Something> result(new Something);
read_something(result.get());
Something* ret=result.get();
result.release();
return ret;
}
void do_test_things(){ //测试各个智能指针
HolderPtr<Something> first(new Something);
first->perform(); //测试箭头操作符
(*first).perform(); //测试解引用操作符

AutoPtr<Something> second(new Something);
second->perform();
(*second).perform();
AutoPtr<Something> tmp(second); //测试缺省的拷贝构造
AutoPtr<Derived> a1(new Derived);
AutoPtr<Something> b1(a1); //测试兼容类型的拷贝构造
second=a1; //测试兼容类型的赋值

CountingPtr<Something> third(new Something);
third->perform();
(*third).perform();
CountingPtr<Something> fouth(third);
CountingPtr<Derived> a2(new Derived);
CountingPtr<Something> b2(a2); //测试兼容类型的拷贝构造
third=a2; //测试兼容类型的赋值
third=new Something; //测试内建指针的赋值
CountingPtr<Something> comp1(new Derived); //测试针对派生类指针的构造函数
CountingPtr<Something> comp2;
comp2=new Derived; //测试针对派生类指针的赋值运算符

if(a2) //测试bool转型
std::cout<<"a2 is not empty."<<std::endl;
if(a2==b2) //测试两个智能指针的内容是否相等
std::cout<<"a2 is equal to b2."<<std::endl;

CountingPtr<MyClass,
MemberReferenceCount<MyClass,size_t,&MyClass::ref_count> >
a3(new MyClass); //测试侵入式的智能指针
a3->perform();
MyClass::SmartPtr b3(a3);
std::cout<<b3->getCounter()<<std::endl;
}
int main(){
HolderPtr<Something> ptr(load_something());
do_test_things();
return 0;
}


从代码中可以看出,类型MyClass使用了侵入式计数器,因此我把管理这个类型的智能指针CountingPtr组合进来,定义成一个简洁的名字。在下面的测试代码中我们就可以看出,使用MyClass::SmartPtr要比使用CountingPtr<MyClass,MemberReferenceCount<MyClass,size_t,&MyClass::ref_count> >简洁得多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: