用 C++ 设计多维数组
我想为多维数组设计一个 C++ 类。我所说的多维是指 1d、2d、3d 等。该类支持逐个元素加法和标量乘法运算符。假设 A 和 B 是该类的实例(具有相同的大小和维度)。我想在这样的表达式中使用对象:
C = A * 2 + B
我想知道的是如何管理内存。在上面的表达式中,A * 2会创建一个该类的临时对象,稍后将其添加到B中。无论如何,添加完成后该临时对象就是垃圾。我写了下面的类,它运行得很好,但我很确定它会泄漏内存。现在我的问题是,
- 如何解决内存问题?设计课程的最佳方式是什么?
是否可以在堆栈而不是堆上分配所需的内存?
类 XArray { int num_dim; int * 变暗; int *index_helper; int 表大小; 双*表; 民众: XArray(const int n, const int *d):num_dim(n), dims(d) { 整数大小=1; for (int i = 0; i < n; i++) { 大小 *= d[i]; } 表大小=大小; 表=新双[大小]; index_helper = 新的 int[n]; }; ~XArray() { 删除[]表; 删除[]索引助手; }; int 暗淡(int d) { 返回暗淡[d]; } 双&运算符()(int i) { 索引助手[0] = i; 返回 get_helper(1,index_helper); } 双&运算符()(int i, int j) { 索引助手[0] = i; index_helper[1] = j; 返回 get_helper(2,index_helper); } 双&运算符()(int i,int j,int k) { 索引助手[0] = i; index_helper[1] = j; index_helper[2] = k; 返回 get_helper(3,index_helper); } XArray 运算符*(double m) { XArray *xa = new XArray(num_dim, dims); for (int i = 0; i < table_size; i++) { xa->table[i] = this->table[i] * m; } 返回*xa; } XArray 运算符+(const XArray &that) { if (num_dim != that.num_dim) { char *msg = 新的 char[100]; sprintf(msg, "XArray::dimensions 在 + 操作中不匹配,预期 %d,找到 %d", num_dim, that.num_dim); 抛出消息; } for (int i = 0; i < num_dim; i++) { if (this->dims[i] != that.dims[i]) { char *msg = 新的 char[100]; sprintf(msg, "XArray::dimension %d 未加工, %d != %d", i, dims[i], that.dims[i]); 抛出消息; } } XArray *xa = new XArray(num_dim, dims); for (int i = 0; i < table_size; i++) { xa->table[i] = this->table[i] + that.table[i]; } 返回*xa; } 私人的: 双& get_helper(int n, int *索引) { 如果(n!= num_dim){ char *msg = 新的 char[100]; sprintf(msg, "XArray::尺寸不匹配,预期 %d,找到 %d", num_dim, n); 抛出消息; } 整数乘数=1; 整数索引 = 0; for (int i = 0; i < n; i++) { if (indices[i] < 0 ||indexs[i] >= dims[i]) { char *msg = 新的 char[100]; sprintf(msg, "XArray::index %d 超出范围,%d 不在 (0, %d) 中", i, Index[i], dims[i]); 抛出消息; } 索引 += 索引[i] * 乘数; 乘数 *= dims[i]; } 返回表[索引]; }
};
I want to design a C++ class for multidimensional arrays. By multidimensional I mean 1d, 2d, 3d, etc. The class supports element by element addition and scalar multiplication operators. Assume A and B are instances of the class (of the same size & dimension). I'd like to use the objects in expressions like this:
C = A * 2 + B
The thing I'm wondered about is how to manage the memory. In the above expression, A * 2 will create a temporary object of the class which is later added to B. Anyways, the temporary object is garbage after the addition is done. I wrote the following class and it works nicely but I am pretty sure it leaks the memory. Now my questions are,
- How can I fix the memory problem? What is the best way to design the class?
Is that possible to allocate the required memory on the stack instead of the heap?
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]; }
};
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我怀念复制构造函数和赋值运算符。您将需要它们来复制内部缓冲区,而不仅仅是复制指针,否则您最终将释放相同的内存两次。
如果您要支持 c++0x,您可能还需要实现移动语义以获得最佳性能。
像您所做的那样抛出动态分配的对象是一个非常糟糕的主意。
在您的运算符*中,您应该在堆上创建对象:
但是,只要您不编写(移动)复制构造函数,这就会崩溃。因为 return 语句将创建一个引用与 xa 相同的内部数据的副本。 xa 将被销毁,它的析构函数将销毁其内部数据。因此,返回的临时对象在内部指向已损坏的数据。
编辑:
链接到有关移动语义或右值引用的信息
I miss copy constructors and assignment operators. You will need these to make copies of the internal buffers and not just copy the pointers or otherwise you will end up freeing the same memory twice.
If you are going to support c++0x you also might want to implement the move semantics for optimal performance.
Throwing dynamically allocated objects like you are doing is a very bad idea.
In your operator* you should create the object on the heap:
However as long as you don't write your (move) copy constructors this will crash. Because the return statement will make a copy that refers to the same internal data as xa. xa will be destroyed and it's destructor will destroy it's internal data. So the temp object that is being returned points internally to destroyed data.
edit:
Link to info about Move semantics or rvalue references
我建议执行以下操作以避免内存泄漏:
operator = ()
设为private
并未实现以避免table
和index_helper
被覆盖(并意外导致内存泄漏)。如果您希望使用它们,那么请确保,在重新分配给避免泄漏。std::string
而不是
char *msg = new
。它更加干净和字符[100];
可维护。
XArray(num_dim, 暗淡);;反而
只需将其声明为 XArray xa; 即可。
它将被堆叠并上紧
自动(如果您有c++0x支持,那么您可以选择完美转发以消除不必要的副本)
现在关于您剩余的
class
设计,答案将非常主观,需要详细的代码分析。所以,我将由你来决定。I would suggest following to avoid memory leaks:
operator = ()
private
and unimplemented to avoidtable
andindex_helper
getting overwritten (and cause memory leak accidently). If you wish you to use them, then make sure, you dodelete[] table
anddelete[] index_helper;
before re-assignment to avoid leak.std::string
instead of
char *msg = new
. It's more cleaner andchar[100];
maintainable.
XArray *xa = new
; insteadXArray(num_dim, dims);
simply declare it as
XArray xa;
.It will be on stack and wound up
automatically (if you have c++0x support then you can opt for perfect forwarding to eliminate unnecessary copies)
Now with regards to your remaining
class
design, the answer will be very subjective and need detail code analysis. So, I will leave it up to you to decide.这个问题可能应该在代码审查中而不是在这里。但设计上的一些事情:
手动管理内存通常不是一个好主意,您应该更喜欢使用现有容器而不是动态分配的数组。构造函数获取传入指针的所有权这一事实并不常见,并且可能会导致用户代码出现问题。目前您不允许某些用途,例如:
对于用户代码来说,这比:
这也意味着用户必须知道
dims
所有权已被获取并且他们无法删除
指针。在内部,我会使用 std::vector 来维护动态内存,因为这将免费为您提供正确的语义。事实上,您需要实现复制构造函数和赋值运算符(或禁用它们),因为当前代码不会泄漏,但可能会尝试双重释放一些内存。请记住三者的规则:如果您提供复制构造函数、赋值运算符或析构函数中的任何一个,您可能应该提供所有三个。
您的
operator*
和operator+
泄漏内存(XArray
对象本身作为内部内存实际上是由缺席处理的复制构造函数,但不要认为这是不创建复制构造函数的原因,相反,您必须创建它)允许抛出
char*
,但出于不同的原因,我建议您不要这样做。异常的类型应该提供有关发生了什么的信息,否则您的用户代码将必须解析异常的内容以确定出了什么问题。即使您决定使用单一类型,请注意,重载解析中使用的相同规则不适用于异常处理,特别是您可能会遇到未发生转换的问题。这也是设计中内存泄漏的另一个可能性,就像在抛出一个指向动态分配内存的指针时,如果用户没有释放内存,或者如果用户实际上没有释放内存,那么您将释放内存的责任委托给了用户。关心异常并只捕获所有内容(catch (...) {}
),您将泄漏错误。如果您的程序中可以这样做,那么最好不要
抛出
,而是断言
(这是一个设计决策:错误的大小有多么糟糕,即使例外也可能发生,或者不应该发生的事情?)。成员
index_helper
不属于该类,它是仅由不同operator()
和get_helper()
共享的实现细节> 方法,您应该将其从类中删除,并且可以在每个operator()
中静态分配它。还有一点奇怪的是,您为 1、2 和 3 维提供单独的operator()
,而代码是通用的来处理任何 维度,而且您是实际上要求用户知道在公共接口之外,有两个操作是被禁止的,这也不是一个很好的设计。This question should probably be in Code Review rather than here. But some things on the design:
It is usually not a good idea to manage memory manually, you should prefer using existing containers instead of dynamically allocated arrays. The fact that the constructor acquires ownership of the passed in pointer is not common, and might lead to problems with user code. Currently you are disallowing some uses, like:
Which is simpler to user code than:
Which also means that the user must be aware that
dims
ownership has been acquired and that they cannotdelete
the pointer.Internally I would use
std::vector
to maintain the dynamic memory, as that will give you the proper semantics for free. As it is you need to implement copy constructor and assignment operator (or disable them) as the current code will not leak, but might try to double free some of the memory. Remember the rule of the three: if you provide any of copy constructor, assignment operator or destructor, you should probably provide all three.Your
operator*
andoperator+
leak memory (theXArray
object itself as the internal memory is actually handled by the absence of the copy constructor, but don't think that this is a reason not to create the copy constructor, on the contrary you must create it)Throwing
char*
is allowed, but I would recommend that you don't for different reasons. The type of the exception should give information as to what happened, else your user code will have to parse the contents of the exception to determine what went wrong. Even if you decide to use a single type, note that the same rules that are used in overload resolution do not apply in exception handling, and in particular you might have problems with conversions not taking place. This is also another potential for a memory leak in your design, as in throwing a pointer to dynamically allocated memory you delegating the responsibility of releasing the memory to your users, if the user does not release the memory, or if the user does not really care about the exception and just catches everything (catch (...) {}
) you will leak the errors.It might be better if instead of
throw
ing youassert
ed, if that is possible in your program (that is a design decision: how bad is the wrong size, something that can happen even if exceptional, or something that shouldn't happen?).The member
index_helper
does not belong to the class, it is an implementation detail that is shared only by the differentoperator()
and theget_helper()
method, you should remove it from the class, and you can allocate it statically in eachoperator()
. It is also a bit strange that you offer separateoperator()
for 1, 2 and 3 dimensions, while the code is generic to handle any dimensions, and also that you are actually requiring the user to know that out of the public interface, two of the operations are forbidden, which is not a great design either.