C++笔记:对象作函数返回值产生的奇妙bug

缘起

笔者在学习模板类的时候,希望实现一个类似于STL的Array class。在其中需要一个深拷贝构造函数和一个重载赋值运算符。于是我写了如下代码:

template<class Elem>
class Array
{
private:
    Elem *_arr = nullptr;
    size_t _capacity = 0;//数组容量

public:
    typedef unsigned int size_t;
    Array(size_t capacity=0 ):_capacity(capacity)
    {
        _arr = new Elem[_capacity];
        cout << "Flag1" << endl;//标记构造函数1
    }
    Array(const Array<Elem>& arr)
    {
        (*this)=arr;//偷懒的做法 
        cout << "FLag2" << endl;//标记拷贝构造函数
    }
    ~Array()
    {
        if(_arr)
            delete[] _arr;
        _arr = nullptr;
        _capacity = 0;
        cout << "Flag3" << endl;//标记析构函数
    }
    size_t size() const { return _capacity; }
    //------------------重点在下面-------------------
    Array<Elem> operator=(const Array<Elem>& arr)
    {
        this->~Array();

        this->_capacity = arr._capacity;
        this->_arr = new Elem[_capacity];

        for (size_t i = 0; i < _capacity;++i)
            this->_arr[i] = arr._arr[i];

        return *this;
    }
    //----------------------------------------------
    Elem &operator[](size_t ix) { return this->_arr[ix]; }
    const Elem &operator[](size_t ix) const { return this->_arr[ix]; }

    const Elem* begin() const { return _arr; }//为了支持基于范围的for循环
    const Elem* end() const { return _arr + 20; }//

接下来我在主函数内调试代码:

int main()
{
    Array<int> a;
    Array<int> b(3);

    a = b;
    return 0;
}

结果控制台就刷了很多Flag3标记然后崩溃了。这说明析构函数在不断被调用。
结果

调试历程

这个bug非常诡异,因为整个代码并没有任何明显的循环或者递归存在。

启动逐语句调试,发现一旦运行到了最后的return *this;语句就会莫名其妙开始执行拷贝构造函数,但拷贝构造函数内的(*this)=arr;又会让他跳回operator=函数内,从而形成了循环,不断执行析构函数。

事实上它仍旧形成了一个递归,不过这个递归是两个函数互相调用形成的。

call
call
call
main
operator=
copyctor

不过为什么会在函数末尾执行拷贝构造函数呢?
我先修改了拷贝构造函数的代码,破坏了bug形成的递归结构:

Array(const Array<Elem>& arr)
{
    this->~Array();
    this->_capacity = arr._capacity;
    this->_arr = new Elem[_capacity];

    for (size_t ix = 0; ix < _capacity; ++ix)
        this->_arr[ix] = arr._arr[ix];
    cout << "FLag2" << endl;
}

再来运行一下:
运行结果
可以看出前两个Flag1和最后两个Flag3都是对象a,b的正常构造-析构过程。而紧接着Flag1后面的第一个Flag3是我们在operator=中显式调用的。而Flag2代表的调用拷贝构造函数内又有调用析构函数。排除我们知道的,这中间还存在一个诡异的析构-拷贝过程。

猜想和论证

究竟是谁调用了拷贝构造函数?要搞清楚这个。可以改进一下标记:

cout <<_capacity << "FLag2" << endl;

运行:
在这里插入图片描述
现在可以作出猜想了,是代表返回值的那个对象作出拷贝。
如果修改返回对象为引用呢?
在这里插入图片描述
一切正常了,这说明返回值本身是一个对象实例,和形参一样。创建这个对象调用了拷贝构造函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值