具有数组属性的 class 的 C++ 复制构造函数
C++ copy constructor for a class with an array attribute
我正在创建一个矩阵模板,运行在编写复制构造函数时遇到了问题。虽然数据似乎从构造函数中正确复制,但返回到主程序的对象没有正确的值(看起来它指向不同的内存地址)。在尝试对此进行调试时,我尝试创建一个极简示例,尽管 st运行gely 这并没有产生相同的错误。我觉得这个问题要么超出了我对 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
。它将使您的生活更轻松,并且比手动方法有更少的陷阱。
长版:你的代码有两个主要问题:
- 您错误地使用了 Flexible-Arrays(这是一个编译器扩展,不是标准的 C++),并且
- 您错误地使用了可变长度数组(这也是一个编译器扩展,不是标准的 C++)
1。灵活的数组成员
您使用的第一个编译器扩展是 c 中的一项功能,称为 flexible arrays:
struct Matrix {
...
T data[];
// ^~~~~~~~~
c 允许在 struct
末尾使用未调整大小的数据数组来表示对象,当由 malloc
分配时,这些对象可能在运行时被赋予动态大小。然而,这不是有效的标准 c++ 并且不建议使用它,因为它根本不适合 C++ 的分配器模型。
这应该改成更连贯的东西。
2。变长数组
您使用的第二个扩展也来自 c,称为 可变长度数组:
Matrix(unsigned int m, unsigned int n) : rows(m), cols(n) {
...
T data[m*n];
这也不是有效的标准 C++。您不能在 C++ 中从运行时值构造数组——句号。数组在编译时已知,并且仅在编译时已知。
此外,这就是您遇到问题的地方,T data[m*n]
正在创建一个名为 data
的 new VLA,shadows 灵活数组也命名为 data
。因此,您定义 T data[m*n]
或 T data[other.length]
的每个函数实际上是在创建 new 数组,写入它们,然后不对它们执行任何操作。这就是您看到不同地址的原因。
建议的修复
使用堆内存,也许 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
。它已经知道如何做 lifetime 并使您免于许多陷阱。如果您已经知道 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
这样的容器。
坦率地说,众所周知,开发人员不善于确定什么对性能有益,什么不有益。底层硬件在推测执行、分支预测、指令流水线和缓存局部性等因素中发挥着最大的作用。堆内存和一些额外的字节拷贝通常是你最不担心的,除非你在一个非常紧密的循环中重复地增长容器。
相反,堆内存很容易移动(例如移动构造函数),因为它是指针复制,而缓冲区存储即使移动也会被整体复制。此外,c++17 引入了具有不同选项的多态分配器,内存资源来自何处——允许更快的分配选项(例如,为 std::vector
分配完整页面的虚拟内存资源)。
即使性能很重要:在尝试优化解决方案之前先尝试解决方案和 profile。不要一开始就浪费精力,因为结果可能会让您大吃一惊。有时在合适的条件下more work can result in faster code。
我建议将您的动态(取消)分配代码移动到特定的受保护方法中。它将帮助您避免内存泄漏、双重释放、无用的重新分配,并使您的构造函数更具可读性。
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;
}
};
我正在创建一个矩阵模板,运行在编写复制构造函数时遇到了问题。虽然数据似乎从构造函数中正确复制,但返回到主程序的对象没有正确的值(看起来它指向不同的内存地址)。在尝试对此进行调试时,我尝试创建一个极简示例,尽管 st运行gely 这并没有产生相同的错误。我觉得这个问题要么超出了我对 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
。它将使您的生活更轻松,并且比手动方法有更少的陷阱。
长版:你的代码有两个主要问题:
- 您错误地使用了 Flexible-Arrays(这是一个编译器扩展,不是标准的 C++),并且
- 您错误地使用了可变长度数组(这也是一个编译器扩展,不是标准的 C++)
1。灵活的数组成员
您使用的第一个编译器扩展是 c 中的一项功能,称为 flexible arrays:
struct Matrix {
...
T data[];
// ^~~~~~~~~
c 允许在 struct
末尾使用未调整大小的数据数组来表示对象,当由 malloc
分配时,这些对象可能在运行时被赋予动态大小。然而,这不是有效的标准 c++ 并且不建议使用它,因为它根本不适合 C++ 的分配器模型。
这应该改成更连贯的东西。
2。变长数组
您使用的第二个扩展也来自 c,称为 可变长度数组:
Matrix(unsigned int m, unsigned int n) : rows(m), cols(n) {
...
T data[m*n];
这也不是有效的标准 C++。您不能在 C++ 中从运行时值构造数组——句号。数组在编译时已知,并且仅在编译时已知。
此外,这就是您遇到问题的地方,T data[m*n]
正在创建一个名为 data
的 new VLA,shadows 灵活数组也命名为 data
。因此,您定义 T data[m*n]
或 T data[other.length]
的每个函数实际上是在创建 new 数组,写入它们,然后不对它们执行任何操作。这就是您看到不同地址的原因。
建议的修复
使用堆内存,也许
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
。它已经知道如何做 lifetime 并使您免于许多陷阱。如果您已经知道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
这样的容器。
坦率地说,众所周知,开发人员不善于确定什么对性能有益,什么不有益。底层硬件在推测执行、分支预测、指令流水线和缓存局部性等因素中发挥着最大的作用。堆内存和一些额外的字节拷贝通常是你最不担心的,除非你在一个非常紧密的循环中重复地增长容器。
相反,堆内存很容易移动(例如移动构造函数),因为它是指针复制,而缓冲区存储即使移动也会被整体复制。此外,c++17 引入了具有不同选项的多态分配器,内存资源来自何处——允许更快的分配选项(例如,为 std::vector
分配完整页面的虚拟内存资源)。
即使性能很重要:在尝试优化解决方案之前先尝试解决方案和 profile。不要一开始就浪费精力,因为结果可能会让您大吃一惊。有时在合适的条件下more work can result in faster code。
我建议将您的动态(取消)分配代码移动到特定的受保护方法中。它将帮助您避免内存泄漏、双重释放、无用的重新分配,并使您的构造函数更具可读性。
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;
}
};