分享
 
 
 

(大卫的阅读笔记)More Effective C++ Item 附2:一个auto_ptr的实现实例

王朝c/c++·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

David's Note: I am not a good writer, and to write a fully new article is a little hard for me. But writing some remark on good articles may be also helpful for others and easier to me. So, from now on, I will try to add a new column “David's Reading” in my blog, and post my thought and idea about good articles I have read.

More Effective C++ Item 附2:一个auto_ptr的实现实例

一个auto_ptr的实现实例

Items M9、M10、E26、E31和E32证明了auto_ptr模板类的非同寻常的作用。不幸的是,目前很少有编译器地提供了一个“正确”的实现(注1)。Items M9和M28大致描述了你怎么自己实现一个,但从事实际项目时有一个更详尽的版本就太好了。

下面是两个auot_ptr的实现。第一个版本文档化了类的接口并在类的定义体外面实现了所有的成员函数。第二个版本将所有的成员函数都实现在定义体内了。在文体上,第二个实现不如第一个,因为它没有将类的接口从实现中分离出来。但auto_ptr只是一个简单的类,所以第二个实现比第一个清晰得多。

这是有专门接口申明的auto_ptr模板:

template<class T>

class auto_ptr {

public:

explicit auto_ptr(T *p = 0); // Item M5 有“explicitfor”

// 的描述

template<class U> // 拷贝构造函数成员模板

auto_ptr(auto_ptr<U>& rhs); // (见Item M28):

// 用另一个类型兼容的

// auto_ptr对象

// 初始化一个新的auto_ptr对象

~auto_ptr();

template<class U> // 赋值操作成员模板

auto_ptr<T>& // (见Item M28):

operator=(auto_ptr<U>& rhs); // 用另一个类型兼容的

// auto_ptr对象给它赋值

T& operator*() const; // 见Item M28

T* operator->() const; // 见Item M28

T* get() const; // 返回包容指针的

// 当前值

T* release(); // 放弃包容指针的

// 所有权,

// 并返回其当前值

void reset(T *p = 0); // 删除包容指针,

// 获得指针p的所有权

private:

T *pointee;

// 大卫注: 为什么要这样? 不解, 在该实现中根本就没有用到auto_ptr<U>的私有成员

template<class U> // 让所有的auto_ptr类

friend class auto_ptr<U>; // 成为友元

};

// 大卫注: 以下是模板类auto_ptr成员函数实现

template<class T>

inline auto_ptr<T>::auto_ptr(T *p)

: pointee(p)

{}

template<class T>

inline auto_ptr<T>::auto_ptr(auto_ptr<U>& rhs)

: pointee(rhs.release())

{}

template<class T>

inline auto_ptr<T>::~auto_ptr()

{ delete pointee; }

// 大卫注: faint, 还可以这样! 模板里面套模板。

template<class T>

template<class U>

inline auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<U>& rhs)

{

if (this != &rhs) reset(rhs.release());

return *this;

}

// 大卫注: 此处需要注意的是assign operation将release形参auto_ptr<U>对象, 上面的拷贝构造也是如此, 以免出现多次delete一个受托管对象的错误, 这也说明我们在传递auto_ptr对象时最好不要传对象, 而要传引用(当然, 你还可以传指针, 不过, 在可以用引用的时候请不要用指针, 因为引用更高效而且更简洁), 否则, 你可能会为程序的行为感到莫名其妙, 受托管的对象可能在你没有意识到的时候被delete了, 这一问题对于基于Reference Count的smart pointer则可能不存在。下面的程序可以说明这一点

/*#include <iostream>

#include <memory>

#include <string>

using namespace std;

void f(auto_ptr<string> apString) // copy-constructor is called

{

cout << "in f()" << endl;

}

int main(int argc, char** argv)

{

auto_ptr<string> apString(new string("abc"));

f(apString);

cout << *apString << endl; // Error! The string object has been deleted by f()!

// To solve this problem, you should use pass apString by reference

auto_ptr<string> apString2;

apString2 = apString; // assign-operator is called

cout << *apString << endl; // Error too!

// To solve this problem, don't use copy constructor of auto_ptr

// the following method is also wrong:

// auto_ptr<string> apString2(*apString);

return 0;

}*/

// 大卫注: 根据以上分析, 我认为auto_ptr的实现应该禁用auto_ptr的copy constructor和assignment, 它使得对象的释放工作变得十分混乱

template<class T>

inline T& auto_ptr<T>::operator*() const

{ return *pointee; }

template<class T>

inline T* auto_ptr<T>::operator->() const

{ return pointee; }

template<class T>

inline T* auto_ptr<T>::get() const

{ return pointee; }

template<class T>

inline T* auto_ptr<T>::release()

{

T *oldPointee = pointee;

pointee = 0;

return oldPointee;

}

template<class T>

inline void auto_ptr<T>::reset(T *p)

{

if (pointee != p) {

delete pointee;

pointee = p;

}

}

以下是所有函数定义在类定义体内的auto_ptr模板。

// 大卫注: 以下代码与上面完全等价,只是将函数实现放在了模板内. jjhou的<STL源码剖析>Ch3借用了以下代码, 但是有删减, 让人有点莫名其妙, 如有不解, 可以参照此处代码。

template<class T>

class auto_ptr {

public:

explicit auto_ptr(T *p = 0): pointee(p) {}

template<class U>

auto_ptr(auto_ptr<U>& rhs): pointee(rhs.release()) {}

~auto_ptr() { delete pointee; }

template<class U>

auto_ptr<T>& operator=(auto_ptr<U>& rhs)

{

if (this != &rhs) reset(rhs.release());

return *this;

}

T& operator*() const { return *pointee; }

T* operator->() const { return pointee; }

T* get() const { return pointee; }

T* release()

{

T *oldPointee = pointee;

pointee = 0;

return oldPointee;

}

void reset(T *p = 0)

{

if (pointee != p) {

delete pointee;

pointee = p;

}

}

private:

T *pointee;

template<class U> friend class auto_ptr<U>;

};

如果你所用的编译器还不支持“explicit”,可以安全地用#define取消它的存在:

#define explicit

这不会造成auto_ptr的任何功能减弱,但导致轻微的安全性减弱。详见Item M5。

如果你的编译器不支持成员模板,你可以使用非模板的auto_ptr拷贝构造函数和赋值操作(描述在Item M28)。这将造成你的auto_ptr在使用上有些小小的不方便,但没有其它方法能模仿成员模板的行为,唉!如果成员模板(或语言中的其它一些特性)对你非常重要,告诉你的编译器提供商。越多的用户要求新的语言特性,提供商将越快地实现它们。

* 注1:

这主要是因为auto_ptr的标准长年来一直没有确定。其最终描述被采纳于1997年11月。其细节可参考本书的主页。注意,此处的auto_ptr版本在实现上比正式版本在具体细节上有小小的省略:实际上auto_ptr位于名字空间std中(见Item M35)并且其成员函数承诺不抛任何异常。

// 大卫注: 原文到此结束, 以下内容由本人添加, 不再特别注明。

根据以上的分析, 上述auto_ptr实现实在有太多不尽人意的地方, 那么颇受好评的SGI STL又是怎么做的呢?

以下内容取自STLport: _auto_ptr.h

#ifndef _STLP_AUTO_PTR_H

# define _STLP_AUTO_PTR_H

_STLP_BEGIN_NAMESPACE

// 加个基类在这里, 有什么特殊用途呢?

class __ptr_base {

public:

void* _M_p;

void __set(const void* p) { _M_p = __CONST_CAST(void*,p); }

void __set(void* p) { _M_p = p; }

};

// 这个类有什么特殊作用吗?

template <class _Tp> class auto_ptr_ref {

public:

__ptr_base& _M_r;

_Tp* const _M_p;

auto_ptr_ref(__ptr_base& __r, _Tp* __p) : _M_r(__r), _M_p(__p) { }

_Tp* release() const { _M_r.__set((void*)0); return _M_p; }

};

template<class _Tp> class auto_ptr : public __ptr_base {

public:

typedef _Tp element_type;

typedef auto_ptr<_Tp> _Self;

_Tp* release() {

_Tp* __px = this->get();

this->_M_p = 0;

return __px;

}

void reset(_Tp* __px=0) {

_Tp* __pt = this->get();

if (__px != __pt)

delete __pt;

this->__set(__px);

}

_Tp* get() const { return __REINTERPRET_CAST(_Tp*,__CONST_CAST(void*,_M_p)); }

# if !defined (_STLP_NO_ARROW_OPERATOR)

_Tp* operator->() const {

_STLP_VERBOSE_ASSERT(get()!=0, _StlMsg_AUTO_PTR_NULL)

return get();

}

# endif

_Tp& operator*() const {

_STLP_VERBOSE_ASSERT(get()!=0, _StlMsg_AUTO_PTR_NULL)

return *get();

}

auto_ptr() { this->_M_p = 0; }

explicit auto_ptr(_Tp* __px) { this->__set(__px); }

#if defined (_STLP_MEMBER_TEMPLATES)

# if !defined (_STLP_NO_TEMPLATE_CONVERSIONS)

template<class _Tp1> auto_ptr(auto_ptr<_Tp1>& __r) {

_Tp* __conversionCheck = __r.release();

this->__set(__conversionCheck);

}

# endif

template<class _Tp1> auto_ptr<_Tp>& operator=(auto_ptr<_Tp1>& __r) {

_Tp* __conversionCheck = __r.release();

reset(__conversionCheck);

return *this;

}

#endif /* _STLP_MEMBER_TEMPLATES */

auto_ptr(_Self& __r) { this->__set(__r.release()); } // 拷贝构造, 同样释放了原对象, 对于普通的smart pointer这是必须的, 但又是容易引起问题的

_Self& operator=(_Self& __r) { // assign operator, 也release了原对象

reset(__r.release());

return *this;

}

~auto_ptr() { /* boris : reset(0) might be better */ delete this->get(); }

auto_ptr(auto_ptr_ref<_Tp> __r) {

this->__set(__r.release());

}

_Self& operator=(auto_ptr_ref<_Tp> __r) {

reset(__r.release());

return *this;

}

# if defined(_STLP_MEMBER_TEMPLATES) && !defined(_STLP_NO_TEMPLATE_CONVERSIONS)

template<class _Tp1> operator auto_ptr_ref<_Tp1>() {

return auto_ptr_ref<_Tp1>(*this, this->get());

}

template<class _Tp1> operator auto_ptr<_Tp1>() {

return auto_ptr<_Tp1>(release());

}

# else

operator auto_ptr_ref<_Tp>()

{ return auto_ptr_ref<_Tp>(*this, this->get()); }

# endif

};

_STLP_END_NAMESPACE

#endif /* _STLP_AUTO_PTR_H */

SGI STL的auto_ptr实现与上面基本相同, 同样也存在copy constructor和assign operator的问题。

而一般的基于Reference count的auto_ptr又是如何实现的呢, look:

template <class T>

class CComPtr

{

public:

typedef T _PtrClass;

CComPtr()

{

p=NULL;

}

CComPtr(T* lp)

{

if ((p = lp) != NULL)

p->AddRef();

}

CComPtr(const CComPtr<T>& lp) // 拷贝构造, 无需对受托对象进行操作, 仅增加引用计数

{

if ((p = lp.p) != NULL)

p->AddRef();

}

~CComPtr()

{

if (p)

p->Release();

}

void Release()

{

IUnknown* pTemp = p;

if (pTemp)

{

p = NULL;

pTemp->Release();

}

}

operator T*() const

{

return (T*)p;

}

T& operator*() const

{

ATLASSERT(p!=NULL);

return *p;

}

//The assert on operator& usually indicates a bug. If this is really

//what is needed, however, take the address of the p member explicitly.

T** operator&()

{

ATLASSERT(p==NULL);

return &p;

}

_NoAddRefReleaseOnCComPtr<T>* operator->() const

{

ATLASSERT(p!=NULL);

return (_NoAddRefReleaseOnCComPtr<T>*)p;

}

T* operator=(T* lp)

{

return (T*)AtlComPtrAssign((IUnknown**)&p, lp);

}

// 对于CComPtr<T>之间的赋值, 不能使用上面的实现

T* operator=(const CComPtr<T>& lp)

{

return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p);

}

// 以下n函数及其实现略

T* p;

};

其中, 函数AtlComPtrAssign实现如下:

ATLINLINE ATLAPI_(IUnknown*) AtlComPtrAssign(IUnknown** pp, IUnknown* lp)

{

if (lp != NULL)

lp->AddRef(); // 增加右操作数的Reference Count

if (*pp)

(*pp)->Release(); // 减小左操作数的Reference Count

*pp = lp; // 给左操作数重新赋值

return lp;

}

从上面可以看出, 基于引用计数的smart pointer实现没有了先前的auto_ptr存在的copy constructor和assign operator的问题。

当然, 以上的讨论并非说我们必须使用基于引用计数的smart pointer, 引用计数毕竟是一种额外的开销, 而且由于需要考虑线程同步, 需要付出更多额外的开销。所以, 最好的解决办法是根据需要进行选择, 在使用auto_ptr时尽量避免传值。但作为一名C++程序员, 将自己分配的对象交给auto_ptr去管理, 你真的放心吗? 反正我是不大用auto_ptr的(诸位能给出一个十分需要auto_ptr, 或应用auto_ptr可以显著优化程序的例子吗?)。

附注:

1. 写完此文, 偶然发现More Effective C++ M28已经给出了与上面类似的结论, 但该文既已写就, 不忍弃之, 故仍然公布于此.

2. 对于文中我存在疑问的地方, 大家有何见解呢?

3. 欢迎批评指正.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有