通用工具Utilities(一):STL中auto_ptr的实现以及auto_ptr_ref的理解

auto_ptr源代码,参考The C++ Standard Library
/* The following code example is taken from the book
 * "The C++ Standard Library - A Tutorial and Reference"
 * by Nicolai M. Josuttis, Addison-Wesley, 1999
 *
 * (C) Copyright Nicolai M. Josuttis 1999.
 * Permission to copy, use, modify, sell and distribute this software
 * is granted provided this copyright notice appears in all copies.
 * This software is provided "as is" without express or implied
 * warranty, and with no claim as to its suitability for any purpose.
 */
/* 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() {
            return *ap;
        }
        T* operator->() const throw() {
            return ap;
        }

        // release ownership
        T* release() throw() {
            T* tmp(ap);
            ap = 0;
            return tmp;
        }

        // reset value
        void reset (T* ptr=0) throw() {
            if (ap != ptr) {
                delete ap;
                ap = 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());
        }
        template<class Y>
        operator auto_ptr<Y>() throw() {
            return auto_ptr<Y>(release());
        }
    };
}

---------------------------------

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();
在我们前面谈到的auto_ptr,它的复制操作的参数类型恰好是非常量引用。所以对于下面的情况它就不能正确处理。
auto_ptr<int> ap1 = auto_ptr<int>(new int(8));//case1 等号右边的是一个临时右值

auto_ptr<int> fun(){//一个生成auto_ptr<int>的source函数
return auto_ptr<int>(new int(8));
}
auto_ptr<int> ap2 ( fun() );//case 2 调用fun()生成的auto_ptr<int>是右值

注:对于case 1,将调用auto_ptr& operator= (auto_ptr& rhs)或template<class Y> auto_ptr& operator= (auto_ptr<Y>& rhs)。但是,根据C++语言的规定,引用不能指向临时对象,除非这个对象是常量。所以case 1是危险的 。。对于case 2,fun()返回一个对象,这个对象也时临时对象,auto_ptr (auto_ptr& rhs) 或 template<class Y> auto_ptr (auto_ptr<Y>&
 rhs) 这两个构造函数参数类型都是引用,因此也是不安全的。

而这种情况不但合法,也是很常见的,我们不能拒绝这种用法,怎么办?天才的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)完成最后的工作。

对这个用法感兴趣的同仁,可以参考SGI的STL源码 :)







auto_ptr机制协助我们把右值转化为左值。这一机制的理论基础是“重载”和“template参数推导规则”之间一个细微的不同处。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值