• 从一个auto_ptr中所学到的东西


    参看vs2008 memory文件里的auto_ptr定义。

    首先可以发现又一个auto_ptr_ref,好奇为什么多出这样一个东西。

    然后查看网上说明

    今天我在看 The C++ Standard Library 的时候,总觉的上面讲的关于auto_ptr_ref的问题没有说清楚,查了网上的资料发现也没有说清。 也许还有很多朋友像我一样对此存在疑问。其实,这个问题有没有弄清楚,对实际编程影像并不大,但是本着“勿在浮沙筑高台”的精神,我用了一个晚上的时间,来个彻底的大调查,终于基本上弄明白了其中的道理。(大多数时间都浪费在VC上,因为 VC支持对右值的非 常应用,而标准C++不可以,所以在VC中,有没有auto_ptr_ref实际上都无所谓!)

    STL中有一个智能指针auto_ptr,可以实现简单的内存自动回收,防治内存泄漏(memory leakage)。auto_ptr实际是一个类,在该类析构时自动调用delete,从而打到了内存回收的效果。

    但是,由于同一个指针同一时刻只能被一个auto_ptr占用,如果采用赋值操作(=)或者拷贝构造函数调用等,就会发生所有权转移,例如:

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

    auto_ptr<int> q;

    此时,p拥有指向一个int的指针,q的指针为空。如果执行

    q=p;

    则,p指向空,q指向int;

    但是,这样所有权转换的问题同样发生在参数传递中,例如

    void foo(auto_ptr<int> t);

    如果调用 foo(p);

    那么p就丢失了指针,所以一个解决方法是用引用

    例如 void foo1(auto_ptr<int>& t);

             void foo2 (const auto_ptr<int>& t);

    两者都是可以的,不过foo1非常不安全,因为在函数里面很容易通过类似赋值的操作使t丢失指针,而foo2不会。例如

    void foo2(const auto_ptr<int>& t)

    { auto_ptr<int> m; m=t;}

    会发生编译错误,从而避免灾难的发生。

    但随之又出现一个很大的问题,就是auto_ptr类的拷贝构造函数,或者赋值函数。

    最理想的情况是这样(如果能成功,就不会有别的什么问题):

    auto_ptr(const auto_ptr& rhs):ap(rhs.release()){}

    但由于上述的原因,会发生编译错误(因为调用了release(),而release()会改变成员变量,不再是const)。

    所以只能去掉const,变为

    auto_ptr(auto_ptr& rhs):ap(rhs.release()){}

    这样可以编译成功,而且往往也能正确运行,但是唯一的问题是: 当rhs为右值时会出现问题。

    为了简化问题,先假设拷贝构造函数什么都不做,即:

    auto_ptr(auto_ptr& rhs){}

    那么,如果有 auto_ptr<int> p(new int(10))

    执行 auto_ptr<int> q(p),不会有任何问题,因为p是左值。

    但如果执行      auto_ptr<int> q(auto_ptr<int>(new int(10))) ,则会发生编译错误,因为auto_ptr<int>(new int(10)) 是右值,对右值的引用只能是常引用,也就是"const auto_ptr& rhs"的形式。但这里要注意的是,刚才那段代码用VC编译没有任何问题,并且可以顺利运行,但是用GCC之类的标准c++就不能顺利编译。

    在VC中

    auto_ptr<int>& p=auto_ptr<int>(new int(0))     是合法的,但在标准C++中是不合法的,只有

    const auto_ptr<int>& p=auto_ptr<int>(new int(0)) 才是合法的,也即在标准C++中,对右值的引用只能是常引用。

    所以说,要在标准C++中实现 auto_ptr<int> p(auto_ptr<int>(new int(0))) 就变得不可能了,因为如上所说,拷贝构造函数是这样的形式:

    auto_ptr(auto_ptr<T>& rhs):ap(rhs.release()){}

    但是不能把右值传到一个非 常引用中。

    但毕竟有聪明的人能想到解决办法,利用代理类( proxy class)

    声明如下结构,为了方便,我用int代替模板参数

    struct auto_ptr_ref

    {

       int* p;

       auto_ptr_ref(int *t):p(t){}

    };

    然后在auto_ptr类中增加了以下东东:

    auto_ptr(auto_ptr_ref rhs):ap(rhs.p){}

    auto_ptr& operator=(auto_ptr_ref rhs){reset(rhs.p); return *this;}

    operator auto_ptr_ref(){return auto_ptr_ref(release());}

    之后,如果在标准C++有以下调用(VC中也会按照这个步骤调用,虽然没有auto_ptr_ref它也能直接调用)

    auto_ptr<int> p(auto_ptr<int>(new int(0)))

    便可以成功,过程如下:

    1. 构造临时对象 auto_ptr<int>(new int(0))

    2. 想将临时对象通过拷贝构造函数传给p,却发现没有合适的拷贝构造函数,因为只有auto_ptr(auto_ptr& rhs)这个不能用,又没有auto_ptr(const auto_ptr& rhs) (因为用了在所有权转移中会出错),呵呵!

    3. 编译器只能曲线救国,看看类型转换后能不能传递。

    4. 由于我们定义了 operator auto_ptr_ref() 所以编译器自然就可以试一下转为 auto_ptr_ref类型。

    5. 编译器猛然间发现,我们定义了 auto_ptr(auto_ptr_ref rhs):ap(rhs.p){} 的构造函数,可以传递。

    6. 顺利构造p,任务完成。

    其实说白了问题很简单,因为构造函数不能接受右值,则取 中间左值=右值, 然后再让函数接受中间左值。 而这一系列过程正是利用编译器能够自动进行类型转换而完成的。

      感觉确实巧妙,然后是接着看,为什么auto_ptr的copy构造函数和operator=多了个模板类型的。其实这里是如果T和other的指针能够隐式转换,那么可以进行这样的操作。

    那么为什么不直接就用一个模板了?原来是copy cons不能作为模板,所以必须要有个非模板类型的,那么下面的就可以编译

    class A {}
    class B : public A {}
    
    B * b = new B();
    A * a = b;       // OK!
    
    
    auto_ptr<B> b(new B);
    auto_ptr<A> a = b; // *

    最后auto_ptr在c++11中废弃了

    为什么?

    解释

    Why deprecate auto_ptr?

    The auto_ptr class template was a pioneer in the area of move semantics. auto_ptr has always had move semantics - the ability to transfer its resource from one object to another. An early auto_ptr design accomplished this transfer using copy syntax and with a const auto_ptr as the source:

    const auto_ptr<int> source(new int);
    auto_ptr<int> target = source;  // move from const source to target
    

    With such a design, one could put auto_ptr into a container:

    vector<auto_ptr<int> > vec;
    

    However field experience with this design revealed subtle problems. Namely:

    sort(vec.begin(), vec.end(), indirect_less());
    

    Depending upon the implementation of sort, the above line of reasonable looking code may or may not execute as expected, and may even crash! The problem is that some implementations of sort will pick an element out of the sequence, and store a local copy of it.

    ...
    value_type pivot_element = *mid_point;
    ...
    

    The algorithm assumed that after this construction that pivot_element and *mid_point were equivalent. However when value_type turned out to be an auto_ptr, this assumption failed, and subsequently so did the algorithm.

    The fix to this problem was to make auto_ptr inhospitable to containers by disallowing "copying" from a const auto_ptr. With such an auto_ptr, one gets a compile time error if you try to put it in a container.

    However that fix really only saves clients from sorting standard containers of auto_ptr. It does not prevent clients from sorting client-defined containers of auto_ptr, or even built-in arrays auto_ptr. For example the following code will compile today:

    #include <algorithm>
    #include <memory>
    
    struct indirect_less
    {
        template <class T>
        bool operator()(const T& x, const T& y)
        {
            return *x < *y;
        }
    };
    
    int main()
    {
        std::auto_ptr<int> ptrs[3];
        ptrs[0].reset(new int(3));
        ptrs[1].reset(new int(2));
        ptrs[2].reset(new int(1));
        std::sort(ptrs, ptrs+3, indirect_less()); // run time error?!
    }
    

    Whether or not it runs correctly is entirely dependent upon the implementation of std::sort. And this does not represent a problem with std::sort. Calling any generic code, whether std or not, that will operate on auto_ptr is risky because the generic code may assume that something that looks like a copy operation, actually is a copy operation.

    Conclusion:

    One should not move from lvalues using copy syntax. Other syntax for moving should be used instead. Otherwise generic code is likely to initiate a move when a copy was intended.

    auto_ptr moves from lvalues using copy syntax and is thus fundamentally unsafe.




  • 相关阅读:
    隐私保护政策
    童真儿童简笔画
    方块十字消
    iOS 判断一断代码的执行时间(从网上看的,自己实现一下)
    iOS BLOCK回调:(妖妖随笔)
    typedef struct
    #define和预处理指令
    UIActivityIndicatorView
    Expected a type 的错误
    iOS 本地化字符串—(妖妖随笔)
  • 原文地址:https://www.cnblogs.com/cavehubiao/p/3456302.html
Copyright © 2020-2023  润新知