繁体   English   中英

具有数组属性的 class 的 C++ 复制构造函数

[英]C++ copy constructor for a class with an array attribute

我正在创建一个矩阵模板,但在编写复制构造函数时遇到了问题。 虽然数据似乎从构造函数中正确复制,但返回到主程序的 object 没有正确的值(看起来它指向不同的 memory 地址)。 在我尝试调试时,我尝试创建一个极简示例,但奇怪的是这并没有产生相同的错误。 我觉得这个问题要么超出了我对 C++ 的理解......要么是由于我以某种方式错过的错字引起的。 谁能发现我做错了什么?

matlib.h

#ifndef MATLIB_H
#define MATLIB_H
#include <iostream>

    namespace matlib{
        template <typename T>
        struct Matrix {
                unsigned int rows;   //number of rows
                unsigned int cols;   //number of columns
                unsigned int length; //number of elements
                T data[];                  //contents of matrix
    
                /* Constructors */
                Matrix(unsigned int m, unsigned int n) : rows(m), cols(n) {
                    length = m*n;
                    T data[m*n];
                    //::std::cout << "Hello from the null constructor!" << ::std::endl;
                    //::std::cout << "rows = " << rows << ", cols = " << cols << ", length = " << length << ::std::endl;
                }
                
                Matrix(const Matrix<T> &mat) {
                    rows = mat.rows;
                    cols = mat.cols;
                    length = mat.length;
                    T data[mat.length];
                    ::std::cout << "Hello from the copy constructor!" << ::std::endl;                
                    for (int i = 0; i < length; ++i) {
                        data[i] = mat.data[i];
                        ::std::cout << "data[" << i << "] = " << data[i] << ", mat.data[" << i << "] = " << mat.data[i] << ::std::endl;
                    }
                } 
    
                //Single element indexing and assigment
                T& operator() (int i, int j) {
                    return data[ i*this->cols + j ];
                }
    
                T& operator() (unsigned int i, unsigned int j) {
                    return data[ i*this->cols + j ];
                }
    
                //Single element indexing and assigment
                T& operator() (int i) {
                    return data[i];
                }
    
                T& operator() (unsigned int i) {
                    return data[i];
                } 
    
                
        };
    
    }
    
    #endif

测试脚本.cpp

#include <iostream>
#include "matlib.h"

int main() {
    float arr[7] = {4, 1, 6, 6, 8, 4, 2};
    matlib::Matrix<float> mat1(1,7);

    //Assign values and print
    std::cout << "mat1 = ";
    for (int i = 0; i < mat1.length; ++i) {
        mat1(i) = arr[i];
        std::cout << mat1(i) << " ";
    }
    std::cout << "\n" << std::endl;

    //Copy the object
    matlib::Matrix<float> mat2 = mat1;

    //Print the copied values
    std::cout << "mat2 = ";
    for (int i = 0; i < mat2.length; ++i) {
        std::cout << mat2(i) << " ";
    }
    std::cout << std::endl;

    return 0;
}

控制台 output:

mat1 = 4 1 6 6 8 4 2 

Hello from the copy constructor!
data[0] = 4, mat.data[0] = 4
data[1] = 1, mat.data[1] = 1
data[2] = 6, mat.data[2] = 6
data[3] = 6, mat.data[3] = 6
data[4] = 8, mat.data[4] = 8
data[5] = 4, mat.data[5] = 4
data[6] = 2, mat.data[6] = 2
mat2 = 9.80909e-45 1.4013e-45 9.80909e-45 9.80909e-45 4 1 6 

我相信很多人会建议涉及“std::vector”的解决方案,尽管这主要是考虑到 HPC 的学习练习。 一旦这有点发展,我可能会添加边界检查。

简短版:只需使用std::vector 它将使您的生活更轻松,并且比手动方法具有更少的陷阱。


长版:您的代码中有两个主要问题:

  1. 您错误地使用了灵活数组(这是编译器扩展,而不是标准 C++),并且
  2. 您错误地使用了可变长度 Arrays(这也是编译器扩展,而不是标准 C++)

1. 灵活的数组成员

您使用的第一个编译器扩展是称为灵活 arrays的功能:

struct Matrix {
    ...
    T data[];
 // ^~~~~~~~~

允许在结构的末尾使用struct来表示在运行时由malloc分配的对象可以在运行时给出动态大小。 然而,这不是有效的标准并且不建议使用它,因为它根本不适合 C++ 的分配器 model。

应该将其更改为更连贯的内容。

2.变长Arrays

您使用的第二个扩展也来自 ,称为变长 arrays

Matrix(unsigned int m, unsigned int n) : rows(m), cols(n) {
    ...
    T data[m*n];

这也不是有效的标准 C++。 您不能从 C++ 中的运行时值构造数组——句号 Arrays 在编译时是已知的,并且仅在编译时是已知的。

此外,这就是您遇到问题的地方, T data[m*n]正在创建一个名为dataVLA,它隐藏了也名为data的灵活数组。 所以每个 function 你定义T data[m*n]T data[other.length] ,你实际上是在创建新的arrays ,写入它们,然后对它们什么都不做。 这就是您看到不同地址的原因。

建议的修复

  1. 使用堆 memory,也许使用std::unique_ptr来为你管理事情。 在构造时分配大小,在复制时克隆它。

     // Construction Matrix(unsigned int m, unsigned int n): rows(m), cols(n), data(std::make_unique<T[]>(m * n)) // where 'data' is std::unique_ptr<T[]> {... }

    这将需要一个自定义的复制构造函数:

     Matrix(const Matrix& other): rows(other.rows), cols(other.cols), data(std::make_unique<T[]>(rows * cols)){ // Copy all elements from 'other' to 'data' std::copy_n(other.get(), rows * cols, data.get()); }

    或者,更好的是:

  2. 使用std::vector 它已经知道如何度过一生,并使您免于许多陷阱。 如果您已经知道vector的最大大小,则可以使用resizereserve + push_back ,这样可以节省重新分配成本。

     Matrix(unsigned int m, unsigned int n): rows(m), cols(n), data(m * n) // where 'data' is std::vector<T> {... }

    使用std::vector你可以这样做:

     Matrix(const Matrix& other) = default;

    在您的 class 声明中,它将使用std::vector的基础复制构造函数。 这是一种更好的方法 IMO。


关于“高性能计算”的单独说明

我鼓励你不要仅仅为了 HPC 的目的而回避像std::vector这样的容器。

坦率地说,开发人员在确定什么对性能有利和不利方面是出了名的差。 底层硬件在推测执行、分支预测、指令流水线和缓存局部性等因素中发挥着最大的作用。 堆 memory 和一些额外的字节副本通常是您最不担心的,除非您在一个非常紧密的循环中反复增长容器。

相反,堆 memory 很容易移动(例如移动构造函数),因为它是指针复制,而缓冲区存储即使移动也会被完全复制。 此外, 引入了具有不同选项的多态分配器,其中 memory 资源来自 - 允许更快的分配选项(例如,用于分配完整页面的虚拟 memory 资源std::vector )。

即使在性能很重要的情况下:在尝试优化之前尝试解决方案和配置文件 不要在前期浪费精力,因为结果可能会让你大吃一惊。 有时做更多的工作可以在正确的条件下产生更快的代码

我建议在特定的受保护方法中移动您的动态(取消)分配代码。 它将帮助您避免 memory 泄漏、双重免费、无用的重新分配,并使您的构造函数更具可读性。

template <typename T>
struct Matrix {
    std::size_t rows{0}, cols{0};
    size_t capacity{0};
    T* data{nullptr};
    
    Matrix() = default;
    Matrix(size_t rows, size_t cols): Matrix()
    {
        this->allocate(rows * cols);
        this->rows = rows;
        this->cols = cols;
    }
    
    Matrix(const Matrix<T>& other): Matrix()
    {
        *this = other;
    }
    
    Matrix& operator=(const Matrix<T>& other) 
    {
        if (this != &other) {
            this->allocate(other.length());
            std::copy_n(other.data, other.length(), data);
            this->rows = other.rows;
            this->cols = other.cols;
        }
        return *this;
    }
    
    ~Matrix()
    {
        this->release();
    }
    
    size_t length() const { return rows * cols; }

    // Access
    T& operator()(size_t row, size_t col) { /*TODO*/ }
    const T& operator()(size_t row, size_t col) const { /*TODO*/ }
    
protected:
    void allocate(size_t reqLength) 
    {
        if (data && capacity >= reqLength) return;
        this->release();
        data = new T [reqLength];
        capacity = reqLength;
    }
    
    void release()
    {
        if (data) delete [] data;
        data = nullptr;
        capacity = 0;
    }    
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM