auto_ptr_ref的奇妙

auto_ptr_ref的奇妙(上)

auto_ptr是目前C++标准中唯一的一个智能指针(smart pointer),主要是用来自动管理指针所指向的内存资源。资源管理是程序设计中非常重要的一部分。资源(resource)是计算机中很宽泛的一个概念,用来表示程序中数量有限,用完就必须归还的东西,比如常见的互斥锁(mutex lock)、文件指针、Win32中的画刷(brush)……,其中内存(memory)是最常见的一种资源,也是我们最先接触,使用最多的一种资源,因此它的地位至关重要。它的地位到底重要到什么程度?对此有两种截然不同的理念:

1.内存资源是如此的重要,以至于我们不能把它们交给计算机来管理,而必须由程序员来管理。

2.内存资源是如此的重要,以至于我们不能把它们交给程序员来管理,而必须由计算机来管理。

JavaC#Eiffel秉承了第二种理念,把内存资源交给计算机管理,避免了程序员在内存管理上易犯的大量错误,从整体上提高了开发的效率,但是是以损失一定执行时间上的效率为代价的。因此这些语言在实时系统、操作系统、语言编译器……等一些对时间要求比较严格的领域中运用的很少。

C语言秉承了第一种理念,C++也随之继承了这种理念,从而也就把内存资源管理交给了程序员,对于高段C程序员,当然是给了他们更多的灵活性,可以编制出非常精美的艺术品,但是对于初学者和不那么高段的C程序员,内存管理却是麻烦的根源,带给他们更多的不是灵活性,而是挥之不去的连环噩梦。比如内存泄漏(memory leak)、野指针(wild pointer)等导致的一些极难察觉的bug,最后的调试除错可能会让我们觉得世界末日到了。【注1

1:不是有一种玩笑说法吗?真正的程序员用C(或C++),我想,难度颇高的,由程序员本人负责的内存管理可能是支持这个观点的一个重要理由。:)

但是在C++中有了另外一个管理内存(甚至资源)的选择,那就是智能指针(smart pointer),资源获取即初始化(resource acquisition is initialization)理念的具体实现。【注2

2:在C++的准标准Boost库中,引入了几个新的智能指针scoped_ptrscoped_arrayshared_ptrshared_arrayweak_ptr,相对于auto_ptr有它们的许多好处,感兴趣的读者可以到www.boost.org去看一看。Andrei Alexandrescu在他的Loki库中也专门用Policy设计实现了一个可以扩展的SmartPtr模板,里面用到的一些技术还是很有价值的,可以到http://sourceforge.net/projects/loki-lib/下载Loki库阅读

标准中auto_ptr的引入主要就是为了解决内存资源管理这个让人难以驯服的怪兽。不过在解决一个问题的同时,也会带来一些新的问题,auto_ptr本身有拥有权(ownership)的转移(transfer)问题,而且它本身不支持“值(value)语义”概念【注3】,因此不能用在STL容器里面作为元素使用,在比较新的编译器中,如果这样使用的话,编译器会阻止你。但是在稍微老一点的编译器中使用的话,很可能会无风无浪的编译通过,在执行的过程中,那么我恭喜你,其时你正在一个很长的雷区裸奔,能够毫发无损通过的概率那就只有天知道了。:)

3:关于这些概念,可以参考我写的《范式的二维平面》。

auto_ptr本身的正确使用在很多书中有详细的讲解和示例,而且我们也可以直接阅读auto_ptr的源代码获得更直观的感受。所以对于它的使用我不想再浪费时间和笔墨,在auto_ptr目前实现中【注4】,我们会看到一个奇怪的类模板auto_ptr_ref,第一次我在阅读《The C++ Standard Library》的时候,看到讲解auto_ptr的时候提到auto_ptr_ref就百思不得其解,说实话,这本书是写得非常清晰易懂的,不过我觉得在auto_ptr这个地方花的笔墨比较吝啬,没有完全把问题讲清楚。而且我看的很多书、文章上也并没有详细讲解这个auto_ptr_ref问题,今天我想来对此深入探讨一下,希望能够抛砖引玉。

4auto_ptr的早期实现中有一些bug,后来Nicolai M. Josuttis 对此认真修正了,并作出了一个实现。读者可以到http://www.josuttis.com/libbook/util/autoptr.hpp.html查看。代码本身并不长,我将它们全列在最下面了。读者最好对照代码看文章。其中关于成员函数模板,我并没有讲解,很多书上都有,主要是为了解决指针之间的转化,特别是对于多态指针。

auto_ptr_ref就像它的名字一样,把一个值转化为引用(reference),具体的说,也就是把一个右值(rvalue)转化为一个左值(lvalue)。【注5】我们可能会很奇怪,C++什么时候还需要用到这种功能?很不幸,为了保证auto_ptr的正确行为和一定的安全性,需要用到这个功能。

5:到底什么是左值,什么是右值?有许多让人混淆,并不明晰的概念表述,我会在下一篇文章中表明我的观点。

我们都知道,auto_ptr在进行复制操作(assignment operator and copy constructor)的时候,资源的拥有权(ownership)会发生转移,也即原来的auto_ptr所指向的内存资源转给了新的auto_ptr,本身已经为空。所以说auto_ptr不支持“值语义”,即被复制的值不会改变。一个例子可能更能说明问题:

auto_ptr<int> ap1(new int(9));

auto_ptr<int> ap2(ap1);// ap1失去拥有权,现在指向空,ap2现在获得指9//内存资源

ap1 = ap2;//ap2失去拥有权,现在指向空,ap1现在获得指向9的内存资源

我们在观察auto_ptrassignment operatorcopy constructor的实现时,也能够发现它的参数是auto_ptr& rhs,而不是auto_ptr const& rhs【注6】。也就是说,auto_ptr进行复制操作的时候,它的引数(argument)必须是一个可以改变的左值(事实是这个值必定被改变)。

6:这种写法你可能不习惯,其实就是const auto_ptr& rhs,我为什么要用这种写法,可以参考我写的《C之诡谲(下)》,就知道我并不是为了标新立异。:)

我们最常见到的,复制操作的参数类型都是引用到常量(reference to const),这正好是为了避免改变传进来的引数(argument)。由于不会改变被引用的值,所以C++标准规定:常量引用可以引用右值。比如下列代码都是合法的:

int const& ri = 60;//60是右值

list<int> const& rl = list<int>();//list<int>()是右值

int fun(){}; int const& rf = fun();//fun()是右值

但是一般引用(非常量引用)是绝对不可以引用右值的。比如下列代码都是非法的:

int& ri = 60;

list<int>& rl = list<int>();

int fun(){}; int& rf = fun();

to be continued!

吴桐写于2003.6.16

最近修改2003.6.16

auto_ptr_ref的奇妙(下)在我们前面谈到的auto_ptr,它的复制操作的参数类型恰好是非常量引用。所以对于下面的情况它就不能正确处理。

auto_ptr<int> ap1 = auto_ptr<int>(new int(8));//等号右边的是一个临时右值

auto_ptr<int> fun()//一个生成auto_ptr<int>的source函数

{return auto_ptr<int>(new int(8))}

auto_ptr<int> ap2 ( fun() );//调用fun()生成的auto_ptr<int>是右值

而这种情况不但合法,也是很常见的,我们不能拒绝这种用法,怎么办?天才的C++库设计者已经为我们想到了这一点,auto_ptr_ref的引入就是为了解决这个问题。仔细观察最下面的auto_ptr实现代码,我们会看到这样几行:

/* special conversions with auxiliary type to enable copies and assignments */

auto_ptr(auto_ptr_ref<T> rhs) throw()

: ap(rhs.yp) {

}

auto_ptr& operator= (auto_ptr_ref<T> rhs) throw() { // new

reset(rhs.yp);

return *this;

}

template<class Y>

operator auto_ptr_ref<Y>() throw() {

return auto_ptr_ref<Y>(release());

}

这就是关键所在了。对于一个auto_ptr右值,不可能为它调用正常的赋值运算符函数和复制构造函数,举个例子说明,对于语句

auto_ptr<int> ap1 = auto_ptr<int>(new int(8));

首先生成临时对象右值auto_ptr<int>(new int(8)),然后使用转型函数模板

template<class Y> operator auto_ptr_ref<Y>() throw()

{ return auto_ptr_ref<Y>(release());};

由auto_ptr<int>(new int(8)),首先调用成员函数release(),然后由获取的原始指针生成另外一个临时对象右值auto_ptr_ref<int>(一个指向动态存储区中8的指针)。这个时候我们再看一个构造函数

auto_ptr(auto_ptr_ref<T> rhs) throw()

: ap(rhs.yp) {

}

它的参数类型不是引用,采用的是传值(by value),所以可以接受右值,这时候,调用auto_ptr_ref<int>默认生成的复制构造函数(copy constructor),用上面最后得到的那个auto_ptr_ref<int>临时对象右值作为引数,生成了rhs,接着auto_ptr_ref<int>临时对象右值自动析构结束生命,后面的ap(rhs.yp)完成最后的工作。

我们的整个探险过程就结束了。最好参考《The C++ Standard Library》中讲解auto_ptr使用的章节一起阅读。这样auto_ptr的使用和所有设计原理对我们就不再神秘了。

附录:auto_ptr的实现代码,来自Nicolai M. Josuttis

/* class auto_ptr

* - improved standard conforming implementation

*/

namespace std {

// auxiliary type to enable copies and assignments (now global)

template<class Y>

struct auto_ptr_ref {

Y* yp;

auto_ptr_ref (Y* rhs)

: yp(rhs) {

}

};



template<class T>

class auto_ptr {

private:

T* ap; // refers to the actual owned object (if any)

public:

typedef T element_type;



// constructor

explicit auto_ptr (T* ptr = 0) throw()

: ap(ptr) {

}



// copy constructors (with implicit conversion)

// - note: nonconstant parameter

auto_ptr (auto_ptr& rhs) throw()

: ap(rhs.release()) {

}

template<class Y>

auto_ptr (auto_ptr<Y>& rhs) throw()

: ap(rhs.release()) {

}



// assignments (with implicit conversion)

// - note: nonconstant parameter

auto_ptr& operator= (auto_ptr& rhs) throw() {

reset(rhs.release());

return *this;

}

template<class Y>

auto_ptr& operator= (auto_ptr<Y>& rhs) throw() {

reset(rhs.release());

return *this;

}



// destructor

~auto_ptr() throw() {

delete ap;

}



// value access

T* get() const throw() {

return ap;

}

T& operator*() const throw() {

auto_ptr_ref的奇妙(下)在我们前面谈到的auto_ptr,它的复制操作的参数类型恰好是非常量引用。所以对于下面的情况它就不能正确处理。

auto_ptr<int> ap1 = auto_ptr<int>(new int(8));//等号右边的是一个临时右值

auto_ptr<int> fun()//一个生成auto_ptr<int>的source函数

{return auto_ptr<int>(new int(8))}

auto_ptr<int> ap2 ( fun() );//调用fun()生成的auto_ptr<int>是右值

而这种情况不但合法,也是很常见的,我们不能拒绝这种用法,怎么办?天才的C++库设计者已经为我们想到了这一点,auto_ptr_ref的引入就是为了解决这个问题。仔细观察最下面的auto_ptr实现代码,我们会看到这样几行:

/* special conversions with auxiliary type to enable copies and assignments */

auto_ptr(auto_ptr_ref<T> rhs) throw()

: ap(rhs.yp) {

}

auto_ptr& operator= (auto_ptr_ref<T> rhs) throw() { // new

reset(rhs.yp);

return *this;

}

template<class Y>

operator auto_ptr_ref<Y>() throw() {

return auto_ptr_ref<Y>(release());

}

这就是关键所在了。对于一个auto_ptr右值,不可能为它调用正常的赋值运算符函数和复制构造函数,举个例子说明,对于语句

auto_ptr<int> ap1 = auto_ptr<int>(new int(8));

首先生成临时对象右值auto_ptr<int>(new int(8)),然后使用转型函数模板

template<class Y> operator auto_ptr_ref<Y>() throw()

{ return auto_ptr_ref<Y>(release());};

由auto_ptr<int>(new int(8)),首先调用成员函数release(),然后由获取的原始指针生成另外一个临时对象右值auto_ptr_ref<int>(一个指向动态存储区中8的指针)。这个时候我们再看一个构造函数

auto_ptr(auto_ptr_ref<T> rhs) throw()

: ap(rhs.yp) {

}

它的参数类型不是引用,采用的是传值(by value),所以可以接受右值,这时候,调用auto_ptr_ref<int>默认生成的复制构造函数(copy constructor),用上面最后得到的那个auto_ptr_ref<int>临时对象右值作为引数,生成了rhs,接着auto_ptr_ref<int>临时对象右值自动析构结束生命,后面的ap(rhs.yp)完成最后的工作。

我们的整个探险过程就结束了。最好参考《The C++ Standard Library》中讲解auto_ptr使用的章节一起阅读。这样auto_ptr的使用和所有设计原理对我们就不再神秘了。

附录:auto_ptr的实现代码,来自Nicolai M. Josuttis

/* class auto_ptr

* - improved standard conforming implementation

*/

namespace std {

// auxiliary type to enable copies and assignments (now global)

template<class Y>

struct auto_ptr_ref {

Y* yp;

auto_ptr_ref (Y* rhs)

: yp(rhs) {

}

};



template<class T>

class auto_ptr {

private:

T* ap; // refers to the actual owned object (if any)

public:

typedef T element_type;



// constructor

explicit auto_ptr (T* ptr = 0) throw()

: ap(ptr) {

}



// copy constructors (with implicit conversion)

// - note: nonconstant parameter

auto_ptr (auto_ptr& rhs) throw()

: ap(rhs.release()) {

}

template<class Y>

auto_ptr (auto_ptr<Y>& rhs) throw()

: ap(rhs.release()) {

}



// assignments (with implicit conversion)

// - note: nonconstant parameter

auto_ptr& operator= (auto_ptr& rhs) throw() {

reset(rhs.release());

return *this;

}

template<class Y>

auto_ptr& operator= (auto_ptr<Y>& rhs) throw() {

reset(rhs.release());

return *this;

}



// destructor

~auto_ptr() throw() {

delete ap;

}



// value access

T* get() const throw() {

return ap;

}

T& operator*() const throw() {

阅读更多
个人分类: C++
上一篇有效运用auto_ptr
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭