zoukankan      html  css  js  c++  java
  • STL swap潜在的危险

    背景:

    在学习C++编程的时候,都使用过标准库(STL)当中的swap,但更多的是swap(int,int)或 者等等一些基本的类型,发散一下是否也可以用来置换自定义的一个类型,比如说某一class(定义一个class相当于定义一个type了),先不从效率上来考虑,看看可行性如何。ps:欢迎讨论。

    正文:

    在STL中的swap大概是这样的实现:

    template<typename T>
    void swap(T& a,T& b)
    {
    T temp(a);
    a = b;
    b = temp;
    }

      在里面很清楚先先调用T类型的拷贝构造函数构造一个临时变量temp,然后利用这个中间变量将a和b进行调换。其中的“=”很阴人。编译器在编译一个空类的时候自动会生成四个缺省的函数,分别是默认构造函数,析构函数,拷贝构造函数,赋值运算符(operator=),取址运算符(operator&)(一对,一个非const的,一个const的)http://topic.csdn.net/t/20051118/12/4402429.html#r_achor在CSDN里面找了个帖子,里面有讨论。有个疑问:都有了赋值运算符函数了,是不是就不需要再重载它了http://topic.csdn.net/u/20111216/10/3890d2aa-6c0c-4472-878f-09132c43ab62.html

     

      在都是缺省的情况下,拷贝构造函数和赋值运算符的功能是一样的,也就是说它们都是浅拷贝,当然当浅拷贝能够满足我们的需求的时候,就没有必要去重载它们了。就比如:

    class myclass
    {
    int val;
    char letter;
    myclass():val(0),letter('a'){}
    };


    里面的成员变量都是基本的类型,而没有涉及到指针,这个时候无论是拷贝构造函数和赋值运算符都能够满足我们的要求。但是:

    class myclass
    {
    public:
    int val;
    char * letter;
    myclass():val(0),letter(NULL){}
    ~myclass()
    {
    if(letter)
    {
    delete [] letter;
    }
    }
    };

      这个时候浅拷贝(这里所谓的浅拷贝包括了拷贝构造函数和运算符重载)显然就不能满足我们的要求,只要有一个对象中途被销毁,另一个就要遭殃了(下图很明确的说明了)。

    image

      缺省的赋值运算符重载不能够满足我们的要求,因此我们要重写。还记得上面提过的swap在STL内的实现吗,对,里面就用到了运算符重载,当调用STL swap来置换两个对象的时候,很有必要根据实际情况对swap做出必要的防范,用心设计好运算符重载。下面的讨论都是基于调用STL swap来置换两个对象且类里面定义了指针变量,就比如上面的第三段代码;至于不含有指针变量的很明显,默认的缺省的运算符重载能够满足我们的要求。

     

    试着写这样一个运算符重载:

    myclass& operator=(const myclass& mc)
    {
    val = mc.val;
    letter = new char[strlen(mc.letter)+1];
    strcpy(letter,mc.letter);
    return *this;
    }

      一眼看上去是对的,上面的代码缺少安全的检测,因此有缺陷:运算符左值即mc的成员变量letter指针不一定是有效的,即可能它是一个空指针,总不能给他强加个程序崩溃后的罪名,运行起来会有错误。改进后:

    myclass& operator=(const myclass& mc)
    {
    val = mc.val;
    if(mc.letter)//先做判断
    {
    letter = new char[strlen(mc.letter)+1];
    strcpy(letter,mc.letter);
    }
    return *this;
    }

    假设有主程序:

    int main()
    {
    myclass mc1;
    mc1.val = 1;
    mc1.letter = new char[40];
    strcpy(mc1.letter,"siyuan");
    myclass mc2;
    mc2.val = 2;
    mc2.letter = new char[40];
    strcpy(mc2.letter,"jialin");
    swap(mc1,mc2);
    cout << mc1.val<< endl;
    cout << mc1.letter<< endl;
    cout << mc2.val<< endl;
    cout << mc2.letter<< endl;
    return 0;
    }

    当运算符左值的变量letter指针和右值变量letter指针都不为空图解,过程很顺利(下图)

    image

    但是左值变量letter指针为空的情况下,结果很意外(下图)

    image

     

    右值变量letter指针为空的情况也一样,很意外(下图)

    image

    上面的情况都忽略了对左值变量letter指针为空情况的处理,进一步改进代码:

    myclass& operator=(const myclass& mc)
    {
    val = mc.val;
    if(mc.letter)
    {
    letter = new char[strlen(mc.letter)+1];
    strcpy(letter,mc.letter);
    }
    else
    letter = NULL;//如果左值letter为空指针,也让右值letter指针为空
    return *this;
    }

    于是对于运算符左右值其中之一为空的情况有了新的结果(下图),swap成功了

    image 

      无论是哪种情况,这种代码都不会有失误的时候,甚至当左右值letter都为NULL的时候都不会有错。细心的你一定会有鬼点子(好点子)来鄙视上面那种做法的繁琐(确实够繁琐)。考虑到

    int main()
    {
    char * m = new char[10];
    strcpy(m,"siyuan");
    char * n = NULL;
    m = (m!=NULL?m:" ");
    n = (n!=NULL?n:" ");
    cout << m << endl;
    cout << n << endl;
    swap(m,n);
    m = (m!=NULL?m:" ");
    n = (n!=NULL?n:" ");
    cout << m << endl;
    cout << n << endl;
    return 0;
    }

      这段程序给我很大的启发,确实,它不用再去分配什么空间之类的东西,同时也不用strcpy,毕竟如果数据量较大的话,这些操作花费的时间是不可忽略的。但是“下面的讨论都是基于调用STL swap来置换两个对象且类里面定义了指针变量”,所以这种思路暂时还不能用在这里。

    image

      《effective C++》里面有种做法,就是设置一个swap成员函数,在里面再调用STL里面的swap来交换letter指针(更聪明,也容易理解,图解了)和其他的数据。所以标准库(STL)中的swap无疑是可以用来置换两个自定义类型的(比如说class),但是从上面啰嗦那么多就知道需要注意的地方太多了,而且效率也不高,因此虽然标准库(STL)强大,我们应该有选择的利用。

    最后总结一下吧:

    拷贝构造函数和赋值运算符缺省的情况下功能是一样,当类中有数据涉及指针的时候,我们一定要重载一个不会抛出异常的赋值运算符函数。

    • 当你执意要利用STL版swap来置换两个自定义类型时,请细心设计你的赋值运算符(operator=)。
    • 在重载赋值运算符(operator=)的时候,一定要小心翼翼copy每一份数据,并且必须做到一个不漏。
    • 当类中涉及太多的指针数据,请选用文章最后说明的方法。

     

    捣乱小子 2011-12-18

    ps:欢迎讨论:)

    更多请访问:http://daoluan.net
  • 相关阅读:
    Delphi的idhttp报508 Loop Detected错误的原因
    Delphi的idhttp报IOHandler value is not valid错误的原因
    华为S5700S-52P-LI-AC千兆网管交换机web登录界面配置
    解决win2003/2008下注册机或破解补丁程序无法运行问题
    SQL拆分(转)
    1602四线驱动
    ADC取样
    Delphi AES加密(转)
    使用Qt开发中国象棋(一):概述
    清除当前文件夹下.svn文件的方法
  • 原文地址:https://www.cnblogs.com/daoluanxiaozi/p/2291972.html
Copyright © 2011-2022 走看看