分享
 
 
 

由一份auto_ptr源代码所引发的思考

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

由一份auto_ptr源代码所引发的思考

Kyle

CPPCN 版权所有

如果我问你,auto_ptr最关键的地方,或者说它与一般指针最不一样的地方在哪里,你一定会说它是一个对象,它与自己所占有的资源密切相关,当自己消亡的时候,它也会将自己所拥有的资源一同释放。很好,它之所以会拥有这一特性,全都要归于析构函数的功劳,在析构函数中,它会进行善后处理,这样也就很好的避免了资源泄漏。当然,引入auto_ptr的原因还不至于这么简单,因为许多资源泄漏的问题主要是由于我们的粗心大意所致,所以只要我们细心一些,还是会避免一些不该发生的资源泄漏问题。那究竟是什么原因让我们引入auto_ptr呢?对了,你一定也想到了,就是在异常处理的时候。Ok,来看下面这个例子:

void foo()

{

ClassA *ptr=new ClassA;

try

{

/* 此处放置可能抛出异常的操作 */

}

catch(…)

{

delete ptr;

throw;

}

delete ptr;

}

上面这个例子使用了一般的指针,你会发现它防止发生资源泄漏的处理是多么复杂,必须在每个catch中进行资源的释放,而且有可能会有许多的catch。天哪,这简直就是灾难,它会使我们的代码很长,不易维护,如果忘记其中的一个,就会产生莫名其妙的错误。既然一般的指针防止资源泄漏会如此的繁琐,那有没有一个办法可以使我们不必操心资源的释放呢,由于在异常发生的时候,会进行堆栈解退,所以我们不必担心作为局部变量的指针本身不被销毁,既然这样,我们干脆建立一个指针对象,好比下面这样:

template<class T>

class auto_ptr1

{

private:

T* ap;

public:

……..

~auto_ptr(); //资源释放

}

当指针被销毁的时候,必然会执行析构函数,那就在析构函数中进行资源的释放不就ok了,呵呵,怎么样,是不是很简单呢?的确,整个逻辑的确很简单,但是如果我们在深入思考一下这个指针对象的特性的话,我们会发现有一个困难的问题等待我们去解决。那下面就让我们来看看会遇到什么困难。

由于在auto_ptr销毁的时候它会自动通过析构函数释放所拥有的资源,那么也就决定了auto_ptr对于资源的独占性,即一个资源只能被一个auto_ptr所指向。这一点应该很好理解,假设有两个auto_ptr指向同一个资源,那当其中一个被销毁的时候,另一个将会指向哪里呢?这种指针往往是最为危险的。既然这样,我们怎样才能保证这种独占性呢,其实也很简单,当对指针进行赋值和复制的时候,剥夺原有指针对资源的拥有权,问题也就迎韧而解了。就好比这样:

auto_ptr<int> p(new int(20));

auto_ptr<int> q;

q=p; //p已经丧失了对资源的拥有权,q现在是p的主人

对于这种一般性的情况,问题似乎已经解决了,下面让我们来看一种特殊但却合理的情况:

auto_ptr<int> foo()

{

auto_ptr<int> p(new int(20));

return p;

}

int main()

{

auto_ptr<int> q(foo());

return 0;

}

你认为上面这种情况怎么样,它是合理的,因为它实现了资源的顺利移交,但是你认为auto_ptr<int> q(foo());这一句应该怎样才能调用成功呢?为了说清这个问题,还需要说说左值和右值以及临时对象的问题。也许你会说左值应该就是能够改变的变量,右值当然就是不能改变的变量喽!对吗?对了一点点,实际上左值是能够被引用的变量,说的通俗点就是有名字的变量,你一定想到了些什么,对了,临时变量就没有名字,即使有你也不会知道,因为它不是由你创建的,编译器会在内部辨别它,但你并不知道,因此临时变量不是左值,而是右值。你也许还会问,那const变量是不是左值呢?根据定义,它有名字,当然就是左值了。因此左值并非一定“可被修改”。但是左值和右值与参数有什么关系吗?我要告诉你的是:有,而且相当密切,因为标准c++规定:若传递给类型为引用的形参的实参是右值的话必须保证形参为const引用。Ok,现在让我们回到原来的问题上,由于foo()按值返回,因此编译器必然会产生一个临时对象,也就是说 auto_ptr<int> q(foo()); 这一句中q的构造函数传入的参数是一个右值,因此若想让这一句成功的调用,它的原型必须是这样的:auto_ptr(const auto_ptr&); 但是这样行吗?显然是不行的,因为我们还要剥夺原有指针对资源的拥有权呢,如果采用const引用,那是无法进行剥夺的,因为你无法修改它。你也许想到了另一个办法,我们只要用mutable修饰核心数据域的话,那么即使它是const也可以进行修改它的核心数据。这个办法看起来似乎不错,但如果我们在考虑一下下面这种情况,你或许会改变你的看法,假设有一个const auto_ptr ,如果我们把它赋值给另一个auto_ptr的话,你说应该是怎样的情况发生,呵呵,当然应该是禁止了,因为你不应该试图去改变一个const对象,即禁止剥夺一个const auto_ptr对资源的拥有权。但是如果按照你的想法,采用mutable的话,这种改变是可以实现的,因此你应该打消采用mutable的念头。那么难道就没有办法了吗?当然有,但是不太容易想到,请看下面这个简单的例子:

class X

{

private:

int value;

public:

X(int v=0)

{

value=v;

}

X(X& a)

{

value=a.value;

a.value=0;

}

int set(int v)

{

value=v;

return value;

}

friend ostream& operator << (ostream& os, const X& x)

{

os<<x.value<<endl;

}

};

X f()

{

X a(100);

return a;

}

int main()

{

X c(f());

cout<<c;

return 0;

}

上面这个例子和我们所遇到的情况有些相似X c(f());这一句是无法调用成功的,而且也不能把复制构造函数的引用参数变为const,因为我们要修改参数。Ok,我们就利用这个简单的例子来解决我们所遇到的问题。既然我们已经想到的一些方案不能达到我们的目的,那我们怎么做呢,对了,我们可以用类型转换函数。下面让我来帮你整理一下思路:

1.我们首先应该先定义一个类型转换层,它的核心数据应该和X的核心数据一样,例如:

struct Y

{

int val;

Y(int v):val(v){}

};

有了这个转换层,我们就可以先把函数f()返回时所生成的临时对象通过一个从X到Y的转换函数转型到Y。然后再通过一个从Y到X的转换函数进行对象的构造。至此,所有问题都得以解决。下面让我们来看一下具体方法。

2.添加一个从X到Y的类型转换函数。如下:

operator Y()

{

Y y(value);

return y;

}

3.添加一个从Y到X的类型转换函数,即只有一个参数的构造函数。

X(Y a)

{

value=a.val;

}

OK,大功告成,你可以把这个例子在你的编译器上实现,果然能够解决所有的问题(在VC上会有一点问题,因为VC在临时对象这一点上对标准C++的支持不够好,用临时对象作参数的时候不加const也可以编译通过),下面我给出auto_ptr的一个实作范例,我想你应该能够理解它了:)

template<class Y>

struct auto_ptr_ref

{

Y* yp;

auto_ptr_ref(Y* rhs):yp(rhs){}

}; //注意这个转换层

template<class T>

class auto_ptr1

{

private:

T* ap;

public:

typedef T element_type;

explicit auto_ptr1(T* ptr=0) throw():ap(ptr){}

auto_ptr1(auto_ptr1& rhs) throw():ap(rhs.release()){}

template<class Y>

auto_ptr1(auto_ptr1<Y>& rhs) throw():ap(rhs.release()){}

auto_ptr1& operator = (auto_ptr1& rhs) throw()

{

reset(rhs.release());

return *this;

}

template<class Y>

auto_ptr1& operator = (auto_ptr1<Y>& rhs) throw()

{

reset(rhs.release());

return *this;

}

~auto_ptr1() throw()

{

delete ap;

}

T* get() const throw()

{

return ap;

}

T& operator *() const throw()

{

return *ap;

}

T* operator ->() const throw()

{

return ap;

}

T* release() throw()

{

T* tmp(ap);

ap=0;

return tmp;

}

void reset(T* ptr=0) throw()

{

if(ap!=ptr)

{

delete ap;

ap=ptr;

}

}

auto_ptr1(auto_ptr_ref<T> rhs) throw():ap(rhs.yp){}

auto_ptr1& operator = (auto_ptr_ref<T> rhs) throw()

{

reset(rhs.yp);

return *this;

}

template<class Y>

operator auto_ptr_ref<Y>() throw()

{

return auto_ptr_ref<Y>(release());

}

template<class Y>

operator auto_ptr1<Y>() throw()

{

return auto_ptr1<Y>(release());

}

};

好了,今天就说到这吧,大家有什么问题可以提出

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有