关于运算符重载和内存泄漏的C ++参考

时间:2012-10-03 22:30:45

标签: c++ memory-leaks reference operator-overloading pass-by-reference

我使用以下代码

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正在疯狂地复制。如果我这样做

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;
}

现在它可以工作,但我的内存泄漏很糟糕。

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

谢谢!

5 个答案:

答案 0 :(得分: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矩阵不会复制

答案 1 :(得分:2)

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

第三个版本很糟糕,你不应该这样做 - 尽可能避免动态分配。

第一个版本不应该比你期望的那么慢 - 假设所有优化都已到位你的编译器支持RVO / NRVO(它可能会这样做)。

另一个替代方法是返回一个智能指针 - 这样在类中创建的对象不会在返回时被复制,而是一个托管指针(这样可以更加高效)。

答案 2 :(得分:1)

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

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

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

除了不使用operator *进行乘法运算以及找到更合适的API来乘以矩阵之外,你可以做很多事情。根据您使用的名称判断,您可以从内循环中取出一些乘法。

答案 3 :(得分: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...
        );
}

答案 4 :(得分:0)

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

通过这种方式,您可以确保不会有不必要的副本。