在c++中设计一个多维数组
memory leaks - Design a Multidimensional Array in C++
我想为多维数组设计一个c++类。所谓多维,我指的是1d, 2d, 3d等等。该类支持逐个元素的加法和标量乘法运算符。假设A和B是类的实例(具有相同大小&维)。我想在下列表达式中使用对象:
C = A * 2 + B
我想知道的是如何管理内存。在上面的表达式中,A * 2将创建一个类的临时对象,然后将其添加到b中。无论如何,添加完成后临时对象是垃圾。我写了下面的类,它工作得很好,但我很确定它泄漏了内存。现在我的问题是,
- 如何解决内存问题?设计课程的最佳方式是什么?
-
是否可以在堆栈上而不是堆上分配所需的内存?
class XArray { int num_dim; int *dims; int *index_helper; int table_size; double *table; public: XArray(const int n, const int *d):num_dim(n), dims(d) { int size = 1; for (int i = 0; i < n; i++) { size *= d[i]; } table_size = size; table = new double[size]; index_helper = new int[n]; }; ~XArray() { delete[] table; delete[] index_helper; }; int dim(int d) { return dims[d]; } double& operator()(int i) { index_helper[0] = i; return get_helper(1, index_helper); } double& operator()(int i, int j) { index_helper[0] = i; index_helper[1] = j; return get_helper(2, index_helper); } double& operator()(int i, int j, int k) { index_helper[0] = i; index_helper[1] = j; index_helper[2] = k; return get_helper(3, index_helper); } XArray operator*(double m) { XArray *xa = new XArray(num_dim, dims); for (int i = 0; i < table_size; i++) { xa->table[i] = this->table[i] * m; } return *xa; } XArray operator+(const XArray &that) { if (num_dim != that.num_dim) { char *msg = new char[100]; sprintf(msg, "XArray::dimensions do not match in + operation, expected %d, found %d", num_dim, that.num_dim); throw msg; } for (int i = 0; i < num_dim; i++) { if (this->dims[i] != that.dims[i]) { char *msg = new char[100]; sprintf(msg, "XArray::dimension %d not mached, %d != %d", i, dims[i], that.dims[i]); throw msg; } } XArray *xa = new XArray(num_dim, dims); for (int i = 0; i < table_size; i++) { xa->table[i] = this->table[i] + that.table[i]; } return *xa; } private: double& get_helper(int n, int *indices) { if (n != num_dim) { char *msg = new char[100]; sprintf(msg, "XArray::dimensions do not match, expected %d, found %d", num_dim, n); throw msg; } int multiplier = 1; int index = 0; for (int i = 0; i < n; i++) { if (indices[i] < 0 || indices[i] >= dims[i]) { char *msg = new char[100]; sprintf(msg, "XArray::index %d out of range, %d not in (0, %d)", i, indices[i], dims[i]); throw msg; } index += indices[i] * multiplier; multiplier *= dims[i]; } return table[index]; }
};
我错过了复制构造函数和赋值操作符。您将需要这些来复制内部缓冲区,而不仅仅是复制指针,否则您将最终释放相同的内存两次。
如果你打算支持c++0x,你可能还想实现move语义以获得最佳性能。
像你这样抛出动态分配的对象是一个非常糟糕的主意。
在你的操作符*中你应该在堆上创建对象:
XArray operator*(double m)
{
XArray xa(num_dim, dims);
for (int i = 0; i < table_size; i++) {
xa->table[i] = this->table[i] * m;
}
return xa;
}
然而,只要你不写你的(移动)复制构造函数,这将崩溃。因为返回语句将生成一个与xa引用相同内部数据的副本。Xa会被销毁,它的析构函数会销毁它的内部数据。因此,被返回的temp对象在内部指向已销毁的数据。
编辑:链接到关于移动语义或右值引用的信息
我建议如下以避免内存泄漏:
- 使复制构造函数和
operator = ()
和private
未实现,以避免table
和index_helper
被覆盖(并导致内存意外泄漏)。如果您希望使用它们,那么请确保,在重新赋值之前执行delete[] table
和delete[] index_helper;
,以避免泄漏。 - 对于错误消息使用
std::string
而不是char *msg = new char[100];
。它更干净和维护。 - 不要使用
XArray *xa = new XArray(num_dim, dims);
;而不是只需将其声明为XArray xa;
。它将被放在堆栈上并上紧发条自动(如果你有c++0x支持,那么你可以选择完美转发,以消除不必要的副本)
现在关于你剩下的class
设计,答案将是非常主观的,需要详细的代码分析。
这个问题应该在Code Review中讨论,而不是在这里。但是有些东西在设计上:
手动管理内存通常不是一个好主意,你应该更喜欢使用现有的容器而不是动态分配的数组。构造函数获得传入指针的所有权这一事实并不常见,这可能会导致用户代码出现问题。目前你不允许一些使用,比如:int dims[3] = { 3, 4, 5 };
XArray array( 3, dims ); // or use sizeof(dims)/sizeof(dims[0]), or other trickery
对于用户代码来说比
更简单int ndims = 5;
int *dims = new int[ndims]
XArray array( ndims, dims );
这也意味着用户必须意识到dims
的所有权已经被获取,他们不能delete
指针。
std::vector
来维护动态内存,因为这将免费为您提供适当的语义。实际上,您需要实现复制构造函数和赋值操作符(或禁用它们),因为当前代码不会泄漏,但可能会尝试双重释放一些内存。记住三个规则:如果您提供复制构造函数、赋值操作符或析构函数中的任何一个,您可能应该提供所有三个。
您的operator*
和operator+
泄漏内存(XArray
对象本身作为内部内存实际上是处理通过缺少复制构造函数,但不要认为这是不创建复制构造函数的理由,相反,您必须创建它)
允许抛出char*
,但出于不同的原因,我建议您不要这样做。异常的类型应该提供关于发生了什么事情的信息,否则您的用户代码将不得不解析异常的内容以确定哪里出了问题。即使您决定使用单一类型,也要注意重载解析中使用的相同规则不适用于异常处理,特别是您可能会遇到转换不发生的问题。这也是你设计中另一个潜在的内存泄漏,因为抛出一个指针指向动态分配的内存,你把释放内存的责任委托给了你的用户,如果用户不释放内存,或者如果用户并不真正关心异常,只是捕获一切(catch (...) {}
),你将泄漏错误。
如果在你的程序中可能出现throw
,而不是assert
ed,可能会更好(这是一个设计决策:错误的大小有多糟糕,即使异常也可能发生的事情,或者不应该发生的事情?)
成员index_helper
不属于类,它是一个实现细节,仅由不同的operator()
和get_helper()
方法共享,您应该从类中删除它,并且可以在每个operator()
中静态分配它。这也有点奇怪,你提供单独的operator()
为1,2和3维度,而代码是通用的处理任何维度,而且你实际上是要求用户知道公共接口之外,两个操作是禁止的,这不是一个伟大的设计。