= 运算符是否在 C++ 中调用构造函数/new?

Does the = operator call the constructor/new in C++?

提问人:McAngus 提问时间:3/9/2017 最后编辑:McAngus 更新时间:3/9/2017 访问量:108

问:

假设我有一个(不可变的)矩阵类,它在构造函数中动态创建一个数组,并在解构函数中删除它。

template <typename T>
class matrix {
private:
    T* data;
public:
    size_t const rows, cols;
    matrix(size_t rows, size_t cols) : rows(rows), cols(cols) {
        data = new T[rows*cols];
    }
    ~matrix() {
        delete [] data;
    }
    //access data
    T& operator()(size_t row, size_t col) {
        return data[row*cols + col];
    }
    matrix<T>& operator=(const matrix<T>& other) {
        //what will this->data contain? do I need to delete anything here?
        //should I call the constructor?
        rows = other.rows;
        cols = other.cols;
        data = new T[rows*cols];
        std::copy(&data[0],&data[0] + (sizeof(T)*rows*cols),&other.data[0]);
        return *this;
    }
}

因为我在函数中没有默认构造函数,所以里面的数据只是垃圾,对吧?即使我有一个默认构造函数,也会调用它吗?我基于此处的示例编写上述代码。请注意,为了简洁起见,我省略了输入验证/边界检查。operator=this

编辑: 我想澄清一下,我只关心这样的电话:

matrix<int> A = B;

其中 B 已初始化。

C++ 数组 oop assign assignment-operator

评论

6赞 juanchopanza 3/9/2017
您应该更关心内存泄漏。
2赞 PaulMcKenzie 3/9/2017
您最新的编辑显示对复制构造函数的调用,而不是赋值运算符。所以我之前的评论是站得住脚的——你需要写一个复制构造函数。
3赞 PaulMcKenzie 3/9/2017
最初的问题是在误以为涉及赋值运算符的情况下提出的。鉴于最新的编辑,它根本不涉及。以“不清楚”结束。
1赞 PaulMcKenzie 3/9/2017
此外,如果您编写了一个复制构造函数(这是正确的),那么您链接到的在赋值运算符中使用的示例或多或少是您编写赋值运算符的方式,即对所有成员使用简单的调用。std::swapswap
1赞 Chris Dodd 3/9/2017
matrix<int> A = B;不是赋值,而是初始化。因此,它根本不使用或调用 -- 它调用复制构造函数。operator=

答:

2赞 R Sahu 3/9/2017 #1

因为我在 operator= 函数中没有默认构造函数,所以这里的数据只是垃圾,对吧?

不。

即使我有一个默认构造函数,也会调用它吗?


但是,存在内存泄漏。您不会解除分配在构造对象时分配的内存。

5赞 Ðаn 3/9/2017 #2

如果您用于存储数据,您的类将变得更加简单std::vector

template <typename T>
class matrix {
   std::vector<T> data;
public:
    size_t const rows, cols;
    matrix(size_t rows, size_t cols) : rows(rows), cols(cols) {
        data.resize(rows*cols);
    }
    //access data
    T& operator()(size_t row, size_t col) {
        return data[row*cols + col];
    }
}

现在,您不再需要担心内存泄漏,也无需编写析构函数、复制构造函数或赋值运算符。

评论

4赞 juanchopanza 3/9/2017
而是调整大小,只是在初始化的列表中。data(rows * cols)
-1赞 Enes Keles 3/9/2017 #3

您可以初始化一个对象,然后用于此对象。在这种情况下,此对象不会是垃圾,因为它已经初始化。matrixoperator=datamatrix

如果您使用的是 的未初始化实例,例如 ,其中已经初始化,这意味着您正在调用由编译器自动生成的复制构造函数。在这两种情况下,都没有垃圾值。operator=matrixmatrix<int> a = b;b

3赞 Remy Lebeau 3/9/2017 #4

假设我有一个(不可变的)矩阵类,它在构造函数中动态创建一个数组,并在解构函数中删除它。

未实现复制构造函数违反了三法则(以及 C++11 中的五法则未实现移动构造函数和移动赋值运算符)。

您的复制分配运算符存在内存泄漏,因为它在分配 'ed 数组之前没有 'ing 旧数组。delete[]datanew[]

请尝试以下操作:

template <typename T>
class matrix {
private:
    T* data;
    size_t const rows, cols;

public:
    matrix(size_t rows, size_t cols) : rows(rows), cols(cols) {
        data = new T[rows*cols];
    }

    matrix(const matrix<T> &src) : rows(src.rows), cols(src.cols) {
        data = new T[rows*cols];
        std::copy(data, &data[rows*cols], src.data);
    }

    /* for C++11:

    matrix(matrix<T> &&src) : rows(0), cols(0), data(nullptr) {
        std::swap(rows, src.rows);
        std::swap(cols, src.cols);
        std::swap(data, src.data);
    }
    */

    ~matrix() {
        delete [] data;
    }

    T& operator()(size_t row, size_t col) {
        return data[(row * cols) + col];
    }

    T operator()(size_t row, size_t col) const {
        return data[(row * cols) + col];
    }

    matrix<T>& operator=(const matrix<T>& other) {
        if (&other != this) {
            delete[] data;
            rows = other.rows;
            cols = other.cols;
            data = new T[rows*cols];
            std::copy(data, &data[rows*cols], other.data);
        }
        return *this;
    }

    /* for C++11:

    matrix<T>& operator=(matrix<T> &&other) {
        delete[] data;
        data = nullptr;
        rows = cols = 0;

        std::swap(rows, other.rows);
        std::swap(cols, other.cols);
        std::swap(data, other.data);

        return *this;
    }
    */
};

但是,复制和交换的习惯用语会更安全:

template <typename T>
class matrix {
private:
    T* data;
    size_t const rows, cols;

public:
    matrix(size_t rows, size_t cols) : rows(rows), cols(cols) {
        data = new T[rows*cols];
    }

    matrix(const matrix<T> &src) : rows(src.rows), cols(src.cols) {
        data = new T[rows*cols];
        std::copy(data, &data[rows*cols], src.data);
    }

    /* for C++11:

    matrix(matrix<T> &&src) : rows(0), cols(0), data(nullptr) {
        src.swap(*this);
    }
    */

    ~matrix() {
        delete [] data;
    }

    T& operator()(size_t row, size_t col) {
        return data[(row * cols) + col];
    }

    T operator()(size_t row, size_t col) const {
        return data[(row * cols) + col];
    }

    void swap(matrix<T>& other) noexcept
    {
        std::swap(rows, other.rows);
        std::swap(cols, other.cols);
        std::swap(data, other.data);
    }

    matrix<T>& operator=(const matrix<T>& other) {
        if (&other != this) {
            matrix<T>(other).swap(*this);
        }
        return *this;
    }

    /* for C++11:

    matrix<T>& operator=(matrix<T> &&other) {
        other.swap(*this);
        return *this;
    }
    */
};

在后一种情况下,复制赋值和移动赋值运算符可以合并为C++ 11中的单个运算符:

matrix<T>& operator=(matrix<T> other) {
    other.swap(*this);
    return *this;
}

或者,你可以遵循零法则,让编译器和 STL 为你完成所有工作:std::vector

template <typename T>
class matrix {
private:
    std::vector<T> data;
    size_t const rows, cols;

public:
    matrix(size_t rows, size_t cols) : rows(rows), cols(cols), data(rows*cols) {
    }

    T& operator()(size_t row, size_t col) {
        return data[(row * cols) + col];
    }

    T operator()(size_t row, size_t col) const {
        return data[(row * cols) + col];
    }
};

因为我在函数中没有默认构造函数,所以里面的数据只是垃圾,对吧?operator=

不可以,因为只能在以前构造的对象上调用,就像任何其他类实例方法一样。operator=

即使我有一个默认构造函数,也会调用它吗?

在您展示的示例中,没有。

我想澄清一下,我只关心这样的电话:

matrix<int> A = B;

该声明根本没有调用。使用的只是语法糖,编译器实际上执行复制构造,就好像你写了这个一样:operator==

matrix<int> A(B);

这需要一个复制构造函数,而您尚未实现该构造函数,并且编译器生成的复制构造函数不足以创建数组的深层副本。

复制分配看起来更像是这样:

matrix<int> A; // <-- default construction
A = B; // <-- copy assignment

matrix<int> A(B); // <-- copy construction
A = C; // <-- copy assignment

评论

0赞 McAngus 3/9/2017
所以在零规则的例子中,并由默认的复制构造函数负责吗?rowscolsdata
0赞 Remy Lebeau 3/9/2017
@McAngus:是的,编译器生成的复制构造函数和复制赋值运算符对于 and 成员和成员来说都足够了,当它是符合 Rule-of-Three/Five/Zero 的类型时,比如 。rowscolsdatastd::vector