auto_ptr_ref的奇妙(上)

类别:编程语言 点击:0 评论:0 推荐:
  auto_ptr_ref的奇妙(上)

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

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

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

Java、C#、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_ptr、scoped_array、shared_ptr、shared_array、weak_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问题,今天我想来对此深入探讨一下,希望能够抛砖引玉。

注4:auto_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_ptr的assignment operator、copy 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

本文地址:http://com.8s8s.com/it/it29235.htm