从方法返回时为什么局部变量中的数据被破坏?

时间:2016-02-20 13:46:48

标签: c++ c++11

在测试我的C ++ 11代码时,我遇到了一个错误,我的原点不明确。我已设法在以下人为的程序中重现该错误。

#include <iostream>

class Vector {
    public:
        double* data;
        Vector(int n) {data = new double[n];};
        ~Vector() {delete[] data;}
};

Vector someMethod() {
    if (true) {
        Vector mat {3};
        if (true) {
            mat.data[0] = 0.1;
            mat.data[1] = 0.2;
            mat.data[2] = 0.3;
        } 
        return mat;
    }
}

int main() {
    Vector mat { someMethod() };
    std::cout << mat.data[0] << std::endl;
    std::cout << mat.data[1] << std::endl;
    std::cout << mat.data[2] << std::endl;
    return 0;

}

以下输出由程序生成:

0
0.2
0.3
*** Error in `./testy': double free or corruption (fasttop): 0x0000000001f75010 ***
Aborted (core dumped)

而输出应为:

0.1
0.2
0.3

看来第一个值已损坏。我尝试了不同的 Vector 长度,总是只有第一个值被破坏的情况。我未能对上述行为提出令人满意的解释。我怀疑这是因为 Vector 对象是在 if()语句的块范围内声明和初始化的,该语句位于的块范围内的someMethod()即可。但是,我不明白为什么这应该是一个问题,如果它确实存在。

修改

两种解决方案都出现了,第一个遵循3/5规则,第二个遵循规则0,起作用。谢谢!但是我仍然对为什么会出现这种情况感到困惑。什么机制导致该值被破坏?当我在main方法的开头定义 Vector 对象时调用默认移动构造函数时,它的默认实现会导致这种行为吗?

2 个答案:

答案 0 :(得分:2)

someMethod生成Vector mat。此Vector已复制到Vector mat内的main。由于您没有指定复制构造函数,因此编译器会为您提供默认的复制构造函数,该复制构造函数只复制Vector具有的每个元素double * data。复制后,Vector的两个指针都指向指向同一数据。然后,mat内的原始someMethod被销毁,这意味着它的析构函数会运行,从而删除两个向量的数据。 someMethod返回后,您会收到Vector mat指针无效的data。然后打印出无效的内存,导致未定义的行为。在这种特殊情况下,未定义的行为决定给你稍微错误的输出,但它可以很容易地引起分段错误。

我希望现在很明显为什么zenith的修正纠正了这个问题。

答案 1 :(得分:1)

您需要关注rule of three/five/zero,即

  

如果一个类需要用户定义的析构函数,用户定义的复制构造函数或用户定义的复制赋值运算符,那么它几乎肯定需要全部三个。

在C ++ 11中,由于添加了移动构造函数和移动赋值运算符,因此它是5而不是3。

因此,通过添加适当的复制构造函数和赋值运算符,您的类应该如下所示:

class Vector {
    public:
        double* data;
        Vector(int n) {data = new double[n];}

        // copy constructor
        Vector(Vector const& source) {
            data = new double[ /* the same `n` as used in to allocate *source.data */ ];
            // copy *data from `source` to this->data
        }

        // copy assignment operator
        Vector& operator=(Vector const& source) {
            delete[] data;
            data = new double[ /* the same `n` as used in to allocate *source.data */ ];
            // copy *data from `source` to this->data
            return *this;
        }

        ~Vector() {delete[] data;}
};

如果需要,您可以添加移动运算符以提高效率。

总结零规则:

  

具有自定义析构函数,复制/移动构造函数或复制/移动赋值运算符的类应专门处理所有权(从Single Responsibility Principle开始)。其他类不应该有自定义析构函数,复制/移动构造函数或复制/移动赋值运算符。

看起来像这样:

#include <memory>

class Vector {
    public:
        std::unique_ptr<double[]> data;
        Vector(int n) {data.reset(new double[n]);}

        // no need to implement move constructor and move assignment operator,
        // the automatically generated ones do the Right Thing because
        // the resource is managed by the unique_ptr

        // copy constructor and copy assignment operator are deleted
        // because unique_ptr enforces the uniqueness of its managed
        // resource

        // if we wanted, we could implement the copy operators to do
        // a deep copy of `data`, then we would just need to declare
        // the move operators `= default` to not have them deleted.

        // no need for destructor: unique_ptr manages the resource
};