正确地复制对象--oeprator=与copy构造函数

额,这个名字有点怪怪的=_=

ok,下面进入正题,为了演示方便,代码只写出简略的部分。

copy构造函数

class Base
{
public:
    Base() {}
    Base(const Base& ) 
    {
        cout<<"Base copy "<<endl;
    }
};
class Derived: public Base
{
public:
    Derived() {}
    Derived(const Derived& ind) {}
};

重点关注一下复制构造函数。
虽然类中没有任何数据–但是我们说了,为了演示方便–现在假设base 类正确地编写了自身的变量复制。
当有下面的代码的时候:

    Derived d;
    Derived s(d);

我们希望它的行为是,会输出

Base copy

当然了,当发生derived class 复制构造时,我们也希望其base class 的复制构造能够正确地运行。

然后真实的行为却是,没有输出。
因为,当我们必须为derived class 编写复制构造函数的时候,必须小心的复制那些base class 的成分[1]。

正确的编写应该是:

class Derived: public Base
{
public:
    Derived() {}
    Derived(const Derived& ind):Base(ind) {} //在这里调用base class 的构造函数
};

与复制相关的另一个类成员函数就是operator=了。

operator=操作符

在上文中,已经指出,当我们必须要自己去编写copy 构造函数时,要小心地复制对象。
在自己编写operator= 操作符时也一样,下面直接给出代码:

class Base
{
public:
    Base() {}
    Base(const Base& ) 
    {
        cout<<"Base copy "<<endl;
    }
    Base& operator = (const Base&) //令operator = 返回自身的引用是一个正确的做法
    //为什么?自己去查查资料吧:)
    {
        cout<<"Base = "<<endl;
        return *this;
    }
};
class Derived: public Base
{
public:
    Derived() {}
    Derived(const Derived& ind):Base(ind) {}
    Derived& operator = (const Derived& ind)
    {
        Base::operator=(ind);  //!!这个不能漏掉!!
        return *this;
    }
};

*类中的输出只是为了更好地看到类的行为,仅为演示而用,实际上没有什么意义。

看到红色的注释那句了吗,注意不要漏掉。

也许你又有疑问,那岂不是以后每次为derived class 编写时,都要自己去编写copy 构造函数和operator = 操作符,即使编译器提供的默认版本就足够满足我们的需求?
这个不用担心,编译器会正确地处理的,不如你自己试试:)

然而,operator = 的情况更复杂一点,要注意在operator = 中处理自我赋值。
现在我们单独取一个类作为例子,并给它安上一个指针,指向某个资源。嗯…………差不多像是这样的:

struct T
{
    int i;
};


class Base
{
    T* p;

    Base(const Base&);
public:
    Base()
    {
        p = new T{3};
    }
    ~Base()
    {
        delete p;
    }
    Base& operator = (const Base& inb)
    {
        delete p;
        p = new T(*inb.p);
        return *this;
    }
    void print()  //这个函数是为了验证结果
    {
        cout<<p->i<<endl;   
    }
};

ok,现在有这样的代码使用它们:

    Base b1;
    Base b2;
    b2 = b1;
    b2 = b2;//注意这句
    b2.print(); 

虽然写的类没什么意义,但是单看使用的话,我们期待它输出3,是吧。
但是真实的行为是:0。
原因就在于,在operator = 中没有正确地处理自我赋值。
当有b2 = b2这样的语句时(看下面注释)

    Base& operator = (const Base& inb)
    {
        delete p;            
        //此时inb == *this, delete p 的时候,其实也删除了自身的资源
        //那么后面的行为就是不确定的了
        p = new T(*inb.p);
        return *this;
    }

怪异的是,运行时竟然没有报错,程序一如既往,兴高采烈地运行,虽然行为不可预知。

那么处理这个问题可以加入自我证同测试:

    Base& operator = (const Base& inb)
    {
        if(this == &inb)    return *this; //自我证同测试
        delete p;
        p = new T(*inb.p);
        return *this;
    }

那么代价是什么?
引入了一个控制分支。
问题是,自我赋值的发生概率会有多大呢?
如果每次都要进行一次判断,势必会影响效率。

于是,聪明的人想到了第二种做法:copy and swap技术

    Base& operator = (const Base& inb)
    {
        T* temp = new T(*inb.p);
        delete p;
        p = temp;
        return *this;
    }

先构造一份副本,然后在交换指针值。
就算自我赋值也不会影响正确的行为。
如果你有顾虑到,当自我赋值的时候,先构造一份自身的副本,然后又给回自己,会不会多此一举?
你想一下自我赋值的发生概率,然后再考虑一下这样处理是否值得:)

[参考资料]
[1] Scott Meyers 著, 侯捷译. Effective C++ 中文版: 改善程序技术与设计思维的 55 个有效做法[M]. 电子工业出版社, 2011.
(条款11:在operator= 中处理“自我赋值”;
条款12:复制对象时勿忘其每一个成分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值