在c++中设计一个多维数组

memory leaks - Design a Multidimensional Array in C++

本文关键字:一个 数组 c++      更新时间:2023-10-16

我想为多维数组设计一个c++类。所谓多维,我指的是1d, 2d, 3d等等。该类支持逐个元素的加法和标量乘法运算符。假设A和B是类的实例(具有相同大小&维)。我想在下列表达式中使用对象:

C = A * 2 + B

我想知道的是如何管理内存。在上面的表达式中,A * 2将创建一个类的临时对象,然后将其添加到b中。无论如何,添加完成后临时对象是垃圾。我写了下面的类,它工作得很好,但我很确定它泄漏了内存。现在我的问题是,

  1. 如何解决内存问题?设计课程的最佳方式是什么?
  2. 是否可以在堆栈上而不是堆上分配所需的内存?

    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对象在内部指向已销毁的数据。

编辑:

链接到关于移动语义或右值引用的信息

我建议如下以避免内存泄漏:

  1. 使复制构造函数operator = ()private未实现,以避免tableindex_helper被覆盖(并导致内存意外泄漏)。如果您希望使用它们,那么请确保,在重新赋值之前执行delete[] tabledelete[] index_helper;,以避免泄漏。
  2. 对于错误消息使用std::string而不是char *msg = new char[100];。它更干净和维护。
  3. 不要使用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维度,而代码是通用的处理任何维度,而且你实际上是要求用户知道公共接口之外,两个操作是禁止的,这不是一个伟大的设计。

相关文章: