【问题标题】:C++ matrix class overloaded operators returning by referenceC++ 矩阵类重载运算符按引用返回
【发布时间】:2012-12-29 14:47:25
【问题描述】:

我正在编写模板矩阵类,当从运算符按值返回时出现堆栈溢出:对于较大的矩阵,+、-、*。我宁愿以某种方式通过引用返回以减轻堆栈并避免额外的复制,但是,我将不得不返回一个用 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 amp)上增强一些矩阵运算。我搜索了一个现有的解决方案,找到了 GPU 加速的线性代数库,但我在其中找不到一个带有 +、-、* 运算符的简单矩阵类。也许有人可以推荐我吗?

【问题讨论】:

  • 你能返回一个共享指针吗?
  • 你听说过pImpl 模式吗?

标签: c++ matrix return binary-operators


【解决方案1】:

三个快速cmets:

  • 传统上,Matrix 类使用动态 分配。您没有显示您的 Matrix 课程,但如果您的 数据是: T myData[n][m]; 您可能希望将其更改为: std::vector myData; ,在构造函数中将其初始化为大小n * m,然后 计算operator[] 中的单个索引(应该 如果要进行任何边界检查,请返回代理)。 或者,您可以使用operator()( int i, int j ) 访问元素:myMatrix( i, j )myMatrix[i][j] 是首选访问取决于您问谁。 尽管此解决方案略微增加了总内存使用量(但 非常轻微),它将堆栈占用空间减少到几个 十几个字节,不管矩阵的大小。
  • 同样传统上,矩阵类没有维度 他们的模板参数的一部分。这是否是一件好事 与否是有争议的。你会得到更好的类型检查(和 编译时的错误,而不是运行时的错误)与您的解决方案, 但如果维度是构造函数的参数,而不是 除了模板参数,您可以从命令行读取它们 或配置文件或其他任何东西。这是经典的安全 与灵活性权衡。 关于您的问题,没有尺寸 模板参数意味着T 类型的所有矩阵都有 同类型。因此,您可以访问矩阵的内部结构 从您的成员函数返回,您不再需要 中级T prod[n][m2]。当然,你可以做所有 Matrixfriend 的实例化,或者简单地使用访问 设置值的函数。无论如何,你确实想要一个 中级T prod[n][m2];这不仅需要很多 堆栈内存,这意味着您必须复制结果。
  • 最后,这是更高级的:在最佳矩阵中 类,operator* 不返回一个矩阵,而是一个助手 类,大致如下: 模板 类 MatrixMultiply { L 常量* myLhs; R 常量* myRhs; 上市: typedef T value_type; 矩阵乘法(L const& lhs,R const& rhs) : myLhs( &lhs ) , myRhs( &rhs ) { } int getX() 常量 { 返回 myLhs->getX(); } int getY() 常量 { 返回 myRhs->getY(); } T get( int i, int j ) 常量 { 返回计算IJ(myLhs,myRhs); } }; 然后,您提供一个模板化的构造函数和赋值运算符 它使用getX()getY()get( i, j )。您的 operator* 也是一个模板,它返回一个 MatrixMultiply: 模板 矩阵乘法 运算符*( L const& lhs, R const& rhs ) { 返回矩阵乘法(lhs,rhs); } (请注意,如果 L::value_typeR::value_type 不是 相同,这不会编译。这是你想要的,除了 错误消息将远未清楚。) 结果是你从来没有真正构建过中间体, 临时矩阵。 可以想象,上面的解决方案被大大简化了。 您需要额外的代码来处理错误,而我不需要 认为并行化是微不足道的。但它避免了 所有中间矩阵的构造,即使在复杂的 表达式。 (可以使用抽象基类使用相同的技术, 说MatrixAccessor,使用纯虚拟吸气剂,并派生 Matrix 和所有像 MatrixMultiply 这样的助手来自 它。恕我直言,这更具可读性,并且错误消息 从编译器肯定会更容易理解。结果将是 只要编译器实际上内联了所有成员 职能。但这是一个很大的if,因为可能有 重要的函数嵌套。)

【讨论】:

  • 感谢您的回答(以及其他人),我现在看到解决方案不会像我想象的那么简单。我认为这需要大量阅读才能继续……
  • @deFenestra 这一切都取决于您想要实现的目标以及性能问题。只需将数据移动到std::vector,并将结果直接放入返回的矩阵,而不是中间的T prod[n][m],就可以解决堆栈溢出问题。我的其余建议实际上只是您可能想要长期考虑的想法。 (如果您正在处理大型矩阵和复杂的表达式,这些中间临时对象最终可能会用完所有堆。在这种情况下,您需要我的最后一个建议。)
【解决方案2】:

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

一种解决方案当然是使用三操作数运算,所以不是:

a = b + c;

你使用一个函数:

添加(a, b, c);

其中 a、b 和 c 是引用。

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

【讨论】:

    【解决方案3】:

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

    我看到解决方案在矩阵构造函数中分配内存的方式。如果您根据矩阵大小将其制作为模板,则矩阵的大小可以从模板参数中得知(我发现制作以矩阵大小作为参数的模板很奇怪。这有什么意义?)。因此,您在构造函数中使用“new”分配内存,并在析构函数中使用“delete”将其删除。因此,该内存将根据在 OOP 中运行良好的 RAII 方法进行分配。然后实现 setElement(i, j, value) 等方法,并在二元运算符中设置新创建的矩阵中的元素并返回它。

    但是我希望你处理一些问题。复制构造函数必须真正复制矩阵而不仅仅是一个指针(或者几个析构函数会尝试破坏相同的内存),或者您可以编写“延迟复制”模型,在更改时实际复制矩阵(参见 wiki)。或者您可以在不实现的情况下将复制构造函数设为私有(以完全防止复制)。 如果由于您不希望库的用户更改矩阵值而不允许创建诸如“setElement”之类的方法,则可以访问私有数据和方法(即使在我们作为参数或新创建的对象中) ) 在此类运算符中,因为您在类方法中。

    如果你需要将原始指针传递给其他计算函数,就像它在“else”部分完成的那样,你可以从一个指针创建一个构造函数,它只会复制一个指针(但这是危险的方式。如果你传递一个指针,你不能无处访问它,因为矩阵类现在是老板)或完全复制数据(速度较慢,但​​您可以将堆栈中的指针或您需要控制的指针传递给那里)取决于您的意愿,析构函数将清除它时矩阵破坏。或者您甚至可以创建私有方法,例如“getRawMatrix()”,它将从矩阵返回指向数据的原始指针并将此指针传递给您的计算函数,或者甚至简单地获取原始数据指针,因为您在矩阵类的方法中.

    通常您在构造函数中分配内存并制作“惰性复制”模型,因为矩阵可能很大。类内部允许访问私有数据和成员,这就是类的创建。希望对你有用。。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-04
      • 1970-01-01
      相关资源
      最近更新 更多