0

我使用以下代码

Matrix operator * (const Matrix & obj)
{
    Matrix m = Matrix();

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
        {
            m[i][j] = 0;

            for (int k = 0; k < 4; k++)
                m[i][j] += _data[i][k] * obj._data[k][j];
        }

    return m;
}

像这样

v1 = m_proj * m_view * m_object * v1;

但我想它是非常未经优化的,因为我认为我在操作员内部创建的新矩阵正在疯狂地复制。如果我这样做

Matrix & operator * (const Matrix & obj)
{
    Matrix m = Matrix();

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
        {
            m[i][j] = 0;

            for (int k = 0; k < 4; k++)
                m[i][j] += _data[i][k] * obj._data[k][j];
        }

    return m;
}

将运算符的返回类型更改为对 Matrix 的引用,整个代码完全停止工作(它编译,只是矩阵没有按应有的方式相乘)。

如果我将其更改为

Matrix & operator * (const Matrix & obj)
{
    Matrix & m = * new Matrix();

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
        {
            m[i][j] = 0;

            for (int k = 0; k < 4; k++)
                m[i][j] += _data[i][k] * obj._data[k][j];
        }

    return m;
}

现在它可以工作了,但我的内存泄漏很严重。

那么我该如何解决这个问题呢?有没有优雅的解决方案?

谢谢!

4

5 回答 5

3

您的函数正在创建一个新对象,它应该按值返回该对象。

您不能返回对局部变量的引用,并且返回对动态分配的内存的引用有多个问题:它什么也解决不了(不会减少表达式创建的对象数量,并且由于动态分配而使每个对象的创建成本更高)并增加内存泄漏。

如果没有类的定义,Matrix则不清楚内存是在数组中处理还是动态分配。如果它是动态分配的并且你有一个 C++11 编译器,你可以实现移动构造和移动分配,所有这些副本的成本都会消失。

在 C++03(以及 C++11)中,您可以实现operator*=并手动处理创建的对象(假设您可以有效地执行此操作):

//v1 = m_proj * m_view * m_object * v1;
Matrix tmp = m_proj;
tmp *= m_view;
tmp *= m_object;
tmp *= v1;
v1 = tmp;

这将创建一个临时文件并将所有乘法应用到位,从而减少副本数量。

无论如何,我不会花太多时间在这上面,因为复制 4x4 矩阵并不昂贵

于 2012-10-03T22:43:10.203 回答
2

第二个版本 - 引用返回的自动存储变量 - 是未定义的行为 - 不要这样做。

第三个版本不好,您不应该这样做 - 尽可能避免动态分配。

第一个版本不应该比您预期的慢很多 - 假设所有优化都到位并且您的编译器支持 RVO/NRVO(它可能支持)。

另一种选择是返回一个智能指针 - 这样在类中创建的对象不会在返回时被复制,而是一个指向它的托管指针(这很容易更有效)。

于 2012-10-03T22:38:47.527 回答
1

你的第一个案例是正确的。它实际上可能是有效的(对于...的某些定义),因为允许编译器优化副本(即如果结果应该去,它可能会创建新对象);这称为“返回值优化”。

您的第二个解决方案是垃圾;您正在返回对堆栈上对象的引用,该引用将在函数返回时消失。我想知道为什么它甚至没有给你警告。

正如您所观察到的,第三种解决方案会泄漏内存,因为没有任何东西可以释放“新”-ed 对象。如果启动 RVO,它并不比第一个更有效,而且实际上效率更低,因为您获得了额外的“动态内存分配”开销。

除了完全不使用 operator* 来进行乘法运算并找到更合适的 API 来将矩阵相乘之外,您对此无能为力。从您使用的名称来看,您可能可以从内部循环中提取一些乘法。

于 2012-10-03T22:42:23.337 回答
1

这个问题似乎更多地是关于优化而不是其他任何事情。

因此,如果您非常担心复制的潜在开销,我不明白为什么还要使用循环。

制作一个接受 16 个参数的矩阵构造函数。

然后:

Matrix operator * (const Matrix & obj)
{
    return Matrix(
        _data[0][0]*_obj._data[0][0] + _data[0][1]*_obj._data[1][0] + _data[0][2]*_obj._data[2][0] + _data[0][3]*_obj._data[3][0], 
        _data[1][0]*_obj._data[0][0] + _data[1][1]*_obj._data[1][0] + _data[1][2]*_obj._data[2][0] + _data[1][3]*_obj._data[3][0], 
        _data[2][0]*_obj._data[0][0] + _data[2][1]*_obj._data[1][0] + _data[2][2]*_obj._data[2][0] + _data[2][3]*_obj._data[3][0], 
        // etc...
        );
}
于 2012-10-03T22:50:05.063 回答
0

为了优化,您可以改用 3 参数函数:void multiply(const Matrix& a, const Matrix& b, Matrix& result)。

这样,您可以确保不会有不必要的副本。

于 2012-10-03T22:46:52.977 回答