c++矩阵类重载操作符按引用返回

C++ matrix class overloaded operators returning by reference

本文关键字:引用 返回 操作符 重载 c++      更新时间:2023-10-16

我正在编写模板化的矩阵类,当从操作符返回值时,我得到堆栈溢出:对于较大的矩阵:+,-,*。我更愿意以某种方式通过引用返回来缓解堆栈并避免额外的复制,但是,我将不得不返回一个用new构造的对象,并打破"对每个new使用delete"的一般规则。由于复制开销和堆栈限制问题,我不能按值返回,而且由于内存泄漏,我也不能按引用返回,那么我应该怎么做呢?

这是我的乘积函数(矩阵包含二维数组元素):

    template<typename T, unsigned int n, unsigned int m> template<unsigned int m2>
Matrix<T,n,m2> Matrix<T,n,m>::operator*(Matrix<T,m,m2>& M) {
    T prod[n][m2];
    if(n*m < GPUAccelerationThreshold)
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m2; j++) {
                prod[i][j] = elems[i][0] * M(0, j); 
                for(int p = 1; p < m; p++)
                    prod[i][j] += elems[i][p] * M(p, j); 
            }
    else {
        array_view<T, 2> product(n, m2, *prod);
        array_view<T, 2> a(n, m, *elems);
        array_view<T, 2> b(m, m2, M.elems[0]);
        parallel_for_each(
            product.extent, 
             [=](index<2> idx) restrict(amp) {
                int row = idx[0];
                int col = idx[1];
                for (int inner = 0; inner < m; inner++) {
                    product[idx] += a(row, inner) * b(inner, col);
                }
            }
        );
        product.synchronize();
    }

    return Matrix<T,n,m2>(prod);
}

我正在写这个类,因为我想提高GPU上的一些矩阵操作(与MS放大器)。我已经搜索了一个现有的解决方案,发现GPU加速线性代数库,但我找不到的是一个简单的矩阵类+,-,*操作符。也许有人能给我推荐一些?

三个快速评论:

  • 传统上,Matrix类使用动态分配。你没有展示你的Matrix课程,但是如果你数据是:<>之前T myData [n] [m];之前您可能需要将其更改为:<>之前std::向量myData;之前,在构造函数中将其初始化为n * m大小计算operator[]中的单项指标(应返回一个代理(如果你想做任何边界检查)。或者,您可以使用operator()( int i, int j )访问一个元素:无论是myMatrix( i, j )还是myMatrix[i][j]是访问的首选,这取决于您询问的对象。虽然这个解决方案稍微增加了总内存使用量(但是)(非常轻微),它将堆栈占用减少到几个12个字节,与矩阵大小无关。
  • 传统上,矩阵类也没有维数模板参数的一部分。这是否是一件好事或者没有是有争议的。您将获得更好的类型检查(和解决方案在编译时(而不是运行时)出现的错误,但是,如果维度是构造函数的参数,那么除了模板参数之外,您还可以从命令行读取它们或者配置文件之类的。这是经典的安全方法Vs.灵活性权衡。关于你的问题,没有维度作为模板参数意味着所有类型为T的矩阵都具有相同的类型。因此你可以进入矩阵的内部从成员函数返回,并且不再需要中间T prod[n][m2]。当然,你可以做所有的实例化Matrix友元,或者直接使用访问设置值的函数。无论如何,您确实想要一个中间T prod[n][m2];这不仅需要很多的on堆栈内存,这意味着你必须复制结果。
  • 最后,这是更高级的:在最佳矩阵中类,operator*不返回一个矩阵,而是一个助手类,沿着下面的线:模板类MatrixMultiply{L const* myls;R const* myRhs;公众:typedef T value_type;MatrixMultiply(L constent & lhs, R constent & rhs)@ @ @ @h & h;{}int getX()常量{返回myLhs -> getX ();}int getY() const{返回myRhs -> getY ();}得到(int i, int j) const{返回calculateIJ(myLhs, myLhs);}};然后提供一个模板化的构造函数和赋值操作符它使用getX(), getY()get( i, j )。你的operator*也是一个模板,它返回一个MatrixMultiply:模板MatrixMultiply算子*(L构造& lhs, R构造& rhs){返回MatrixMultiply(lhs, rhs);}(注意,如果L::value_typeR::value_type不是同样的,这不会编译。这就是你想要的,除了错误信息将远不清楚。)结果是你从来没有真正构建中间产物,临时的矩阵。可以想象,上面的解决方案被大大简化了。您需要额外的代码来处理错误,而我不需要认为并行化是微不足道的。但它避免了构造所有中间矩阵,即使是复杂的表达式。(同样的技术可以使用抽象基类,例如MatrixAccessor,带有纯虚getter和派生Matrix和所有像MatrixMultiply这样的助手它。恕我直言,这是可读性强得多,错误信息从编译器肯定会更容易理解。结果将是只要编译器实际上内联了所有的成员功能。但这是一个很大的if,因为可能存在重要的函数嵌套。)

没有简单的方法来解决这个问题。您不能返回堆栈局部变量作为引用,因为当您返回时,变量"后面"的内存将消失。所以你必须在某个地方有一些专用的存储空间。它不一定来自new/delete,但在复制数据时确实需要某种存储。

一个解决方案当然是有三个操作数的操作,所以不是:

a = b + c;

你使用了一个函数:

add(a, b, c);

,其中a, b和c是引用。

它确实使代码更加混乱,但我想不出任何更明显的方法来解决这个问题-如果你不想编写自己的分配器/删除函数(或垃圾收集器)。

其实我不能完全理解你的想法…二进制操作符接受两个参数并创建结果。实际上,您可以看到您正在返回一个新创建的对象。所以这是编写程序的唯一方法:分配内存,使用它,然后删除它。事实上,我甚至不明白你的构造函数是做什么的。如果它只是复制指针到"prod",那么当你从函数返回它时,结果矩阵就会被破坏,因为"prod"内存将在函数返回时被删除(因为它是在堆栈上创建的)。所以你不能通过引用返回它。

我看到的解决方案是在矩阵构造函数中分配内存。如果您根据矩阵大小将其作为模板,则可以从模板参数中了解矩阵的大小(我发现将矩阵大小作为参数制作模板非常奇怪…这有什么意义呢?)所以你用new在constructor中分配内存,用delete在destructor中删除内存。因此,这些内存将根据RAII方法进行分配,这种方法在OOP中工作得非常好。然后实现setElement(i, j, value)等方法,并在二进制操作符中设置新创建矩阵中的元素并返回它。

但是有一些问题我想让你处理。复制构造函数必须真正地复制矩阵,而不仅仅是一个指针(或者几个析构函数将试图破坏相同的内存),或者您可以编程"延迟复制"模型,该模型在更改时实际复制矩阵(参见wiki)。或者您可以在没有实现的情况下将复制构造函数设为私有(以完全防止复制)。如果你不允许创建像"setElement"这样的方法,因为你不希望你的库的用户改变矩阵值,你可以访问这些操作符中的私有数据和方法(甚至在我们作为参数或新创建的对象中),因为你在类方法中。

如果你需要将原始指针传递给其他计算函数,就像在"else"部分所做的那样,你可以从一个指针创建一个构造函数,它只会复制一个指针(但这是一种危险的方式)。如果你传递一个指针,你不能访问它无处,因为矩阵类现在是老板)或复制数据完全(这是较慢的,但你可以传递那里的指针从堆栈或指针,你需要控制后),根据你的愿望,和析析函数将清理它时,矩阵破坏。或者你甚至可以创建私有方法,如"getRawMatrix()",它将从矩阵返回原始数据指针,并将该指针传递给你的计算函数,或者甚至简单地获得原始数据指针,因为你在矩阵类的方法中。

通常你在构造函数中分配内存并创建"延迟复制"模型,因为矩阵可能很大。类内部允许访问私有数据和成员,这就是类的构成。