[英]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
。 它将使您的生活更轻松,并且比手动方法具有更少的陷阱。
长版:您的代码中有两个主要问题:
您使用的第一个编译器扩展是c 中称为灵活 arrays的功能:
struct Matrix {
...
T data[];
// ^~~~~~~~~
c允许在结构的末尾使用struct
来表示在运行时由malloc
分配的对象可以在运行时给出动态大小。 然而,这不是有效的标准c++并且不建议使用它,因为它根本不适合 C++ 的分配器 model。
应该将其更改为更连贯的内容。
您使用的第二个扩展也来自c ,称为变长 arrays :
Matrix(unsigned int m, unsigned int n) : rows(m), cols(n) {
...
T data[m*n];
这也不是有效的标准 C++。 您不能从 C++ 中的运行时值构造数组——句号。 Arrays 在编译时是已知的,并且仅在编译时是已知的。
此外,这就是您遇到问题的地方, T data[m*n]
正在创建一个名为data
的新VLA,它隐藏了也名为data
的灵活数组。 所以每个 function 你定义T data[m*n]
或T data[other.length]
,你实际上是在创建新的arrays ,写入它们,然后对它们什么都不做。 这就是您看到不同地址的原因。
使用堆 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()); }
或者,更好的是:
使用std::vector
。 它已经知道如何度过一生,并使您免于许多陷阱。 如果您已经知道vector
的最大大小,则可以使用resize
或reserve
+ 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 很容易移动(例如移动构造函数),因为它是指针复制,而缓冲区存储即使移动也会被完全复制。 此外, c++17引入了具有不同选项的多态分配器,其中 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.