提问人:StillUsesFORTRAN 提问时间:2/19/2021 更新时间:2/19/2021 访问量:789
具有数组属性的类的 C++ 复制构造函数
C++ copy constructor for a class with an array attribute
问:
我正在创建一个矩阵模板,在编写复制构造函数时遇到了问题。虽然数据似乎是从构造函数中正确复制的,但返回到主程序的对象没有正确的值(看起来像它指向不同的内存地址)。在我尝试调试它时,我尝试创建一个极简主义示例,但奇怪的是,这并没有产生相同的错误。我感觉这个问题要么超出了我对 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
testscript.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;
}
控制台输出:
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++),并且
- 您错误地使用了可变长度数组(这也是一个编译器扩展,而不是标准的 C++)
1. 灵活的阵列成员
您使用的第一个编译器扩展是 c 中称为灵活数组的功能:
struct Matrix {
...
T data[];
// ^~~~~~~~~
C 允许在 A 的末尾使用未大小的数据数组来表示在运行时可能被赋予动态大小的对象。然而,这不是有效的标准 c++,不建议使用它,因为它根本不适合 C++ 的分配器模型。struct
malloc
这应该被改掉,以更连贯地改变。
2. 可变长度数组
您使用的第二个扩展也是来自 c,称为可变长度数组:
Matrix(unsigned int m, unsigned int n) : rows(m), cols(n) {
...
T data[m*n];
这也不是有效的标准C++。不能从 C++ 中的运行时值构造数组 -- 句号。数组在编译时是已知的,并且仅在编译时是已知的。
此外,这就是您遇到问题的地方,正在创建一个名为的新 VLA,该 VLA 隐藏了也称为 的灵活阵列。因此,您定义的每个函数 或 实际上都是在创建新数组,写入它们,然后不对它们执行任何操作。这就是您看到不同地址的原因。T data[m*n]
data
data
T data[m*n]
T data[other.length]
建议的修复
使用堆内存,也许可以为您管理事物。在构造时分配大小,在复制时克隆它。
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;
在类声明中,它将使用 的 底层复制构造函数。这是IMO更好的方法。
std::vector
关于“高性能计算”的单独说明
我鼓励你不要回避容器,就像纯粹为了 HPC 的目的一样。std::vector
坦率地说,开发人员在确定什么对性能有好处和什么对性能不利方面是出了名的糟糕。底层硬件在推测执行、分支预测、指令流水线和缓存位置等因素中起着最大的作用。堆内存和一些额外的字节拷贝通常是您最不担心的,除非您在非常紧密的循环中反复增加容器。
相反,堆内存很容易移动(例如移动构造函数),因为它是一个指针复制,而缓冲区存储即使对于移动也会被全部复制。此外,c++17 还引入了具有不同选项的多态分配器,其中内存资源来自这些选项,从而允许更快的分配选项(例如,为内存资源分配整页的虚拟内存资源)。std::vector
即使在性能很重要的情况下:在尝试优化解决方案之前,先尝试解决方案和配置文件。不要在前期浪费精力,因为结果可能会让你大吃一惊。有时,在适当的条件下,做更多的工作可以加快代码速度。
评论
std::vector
std::vector
我建议在特定的受保护方法中移动动态(取消)分配代码。它将帮助您避免内存泄漏、双重释放、无用的重新分配,并使您的构造函数更具可读性。
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;
}
};
评论
std::unique_ptr<T[]>
new
delete
std::vector
std::vector
下一个:打印副本构造函数 OOP 的问题
评论
Matrix::data
T data[]
T data[m*n];
data = new T[m*n];