编码实践:在矩阵乘法中按值或参考值返回?

时间:2009-01-25 10:05:49

标签: c++ reference scope return

我正在写这个问题,参考我昨天写的this one。经过一些文档记录后,我觉得我想做的事情(以及我认为可行的事情)几乎是不可能的,如果不是不可能的话。有几种方法可以实现它,因为我不是一个经验丰富的程序员,我问你会选择哪种方式。我再次解释我的问题,但现在我有一些解决方案可以探索。

我需要什么

我有一个Matrix类,我想在矩阵之间实现乘法,以便类的使用非常直观:

Matrix a(5,2);
a(4,1) = 6 ;
a(3,1) = 9.4 ;           
...                   // And so on ...

Matrix b(2,9);
b(0,2) = 3;
...                   // And so on ...

// After a while
Matrix i = a * b;

昨天我有什么

目前我重载了两个运算符operator*operator=,直到昨天晚上才以这种方式定义:

Matrix& operator*(Matrix& m);
Matrix& operator=(Matrix& m);

operator *在堆上实例化一个新的Matrix对象(Matrix return = new Matrix(...)),设置值然后只是:

return *result;

我今天拥有的

discussion之后我决定以“不同的方式”实现它,以避免用户被任何类型的指针打扰并保持使用不变。 “不同的方式”是通过值传递operator *的返回值:

Matrix operator*(Matrix& m);
Matrix& operator=(Matrix& m);

operator *在堆栈上实例化return,设置值然后返回对象。

这种方法存在问题:它不起作用。运营商=期望Matrix&和operator *返回一个Matrix。此外,这种方法对我来说看起来并不那么好,原因是另一个原因:我正在处理矩阵,这可能非常大,而且这个库的目标是1)对我的项目来说足够好2)快,所以可能通过按价值不应该是一种选择。

我探讨了哪些解决方案

嗯,按照之前discussion中的建议,我读了一些关于智能指针的内容,它们看起来很棒,但我仍然无法弄清楚如何用它们来解决我的问题。他们处理内存释放和指针复制,但我基本上使用引用,所以他们看起来不适合我。但我可能错了。

也许唯一的解决方案就是传递价值,也许我无法获得效率和良好的界面。但同样,你是专家,我想知道你的意见。

5 个答案:

答案 0 :(得分:9)

你遇到的问题是表达式a * b创建了一个临时对象,而在C ++中,临时不允许绑定到非常量引用,这就是你的Matrix& operator=(Matrix& m)需要。如果您将其更改为:

Matrix& operator=(Matrix const& m);

代码现在应该编译。除了生成可编译代码:)的明显好处之外,添加const还会向您的调用者传达您不会修改参数m,这可能是有用的信息。

您也应该对operator*()

执行相同的操作
Matrix operator*(Matrix const& m) const;

[编辑:最后的额外const表示该方法承诺不会改变左侧的对象*this 。这对于处理诸如a * b * c之类的表达式是必要的 - 子表达式a * b创建一个临时表达式,并且在最后没有const的情况下不会绑定。感谢Greg Rogers在评论中指出这一点。]

P.S。 C ++不允许临时绑定到非常量引用的原因是因为临时存在(顾名思义)只有很短的时间,并且在大多数情况下,尝试修改它们是错误的。

答案 1 :(得分:9)

你应该真正阅读Scott Meyers的Effective C++,它有很多主题。 如前所述,operator=operator*的最佳签名是

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

但我不得不说你应该在

中实现乘法代码
Matrix& operator*=(Matrix const& m);

并在operator*

中重复使用它
Matrix operator*(Matrix const &m) const {
    return Matrix(*this) *= m;
}

这样,用户可以在不想创建新矩阵的情况下成倍增加。 当然,为了使这个代码工作,你也应该有复制构造函数:)

答案 2 :(得分:3)

注意:从Vadims建议开始。如果我们仅讨论非常小的矩阵,则以下讨论没有实际意义,例如:如果你自己限制为3x3或4x4矩阵。此外,我希望我不是想把很多想法塞进你身上:))

由于矩阵可能是一个重物,你绝对应该避免使用深拷贝。智能指针是实现它的一个实用程序,但它们无法解决您的问题。

在您的方案中,有两种常见方法。在这两种情况下,任何副本(如a=b),只复制引用,并递增引用计数器(这是智能指针可以为您做的)。

使用写入时复制,深度复制将延迟,直到进行修改为止。例如在void Matrix.TransFormMe()上调用b之类的成员函数会看到实际数据被两个对象(a和b)引用,并在进行转换之前创建一个深层副本。

净效果是你的矩阵类就像一个“普通”对象,但实际制作的深拷贝数量会大大减少。

另一种方法是不可变对象,其中API本身永远不会修改现有对象 - 任何修改都会创建一个新对象。因此,不是void TransformMe()' member transforming the contained matrix, Matrix contains only a Matrix GetTransformed()`成员,而是返回数据的副本。

哪种方法更好取决于实际数据。在MFC中,CString是写时复制,在.NET中String是不可变的。不可变类通常需要一个构建器类(如StringBuilder),它可以避免许多顺序修改的副本。 Copy-On-Write对象需要仔细设计,以便在API中清楚哪个成员修改内部成员,哪些成员返回副本。

对于矩阵,由于有许多算法可以就地修改矩阵(即算法本身不需要复制),因此写时复制可能是更好的解决方案。

我曾尝试在boost智能指针之上构建一个写时复制指针,但是我还没有触及它来找出线程问题等.Pseudocode看起来像这样:

class CowPtr<T>
{
     refcounting_ptr<T> m_actualData;
   public:
     void MakeUnique()
     {
        if (m_actualData.refcount() > 1)
           m_actualData = m_actualData.DeepCopy();
     }
     // ...remaining smart pointer interface...
}

class MatrixData // not visible to user
{
  std::vector<...> myActualMatrixData;
}

class Matrix
{
  CowPtr<MatrixData> m_ptr; // the simple reference that will be copied on assignment

  double operator()(int row, int col)  const
  {  // a non-modifying member. 
     return m_ptr->GetElement(row, col);
  }

  void Transform()
  {
    m_ptr.MakeUnique(); // we are going to modify the data, so make sure 
                        // we don't modify other references to the same MatrixData
    m_ptr->Transform();
  }
}

答案 3 :(得分:3)

  

...除了const之外都是const,因为他们都打电话(如有必要):

void lupp();
     

更新缓存的LUP。同样代表调用get_inverse()的{​​{1}}并设置lupp()。这会导致问题:

Matrix* Matrix::inverse
     

技术。

请解释这是如何导致问题的。通常不应该这样。此外,如果使用成员变量来缓存临时结果,请将它们设为Matrix& operator=(Matrix const& m); Matrix operator*(Matrix const& m); 。然后,您甚至可以在mutable个对象中修改它们。

答案 4 :(得分:0)

是的,你的建议都很好,我承认我不知道非const引用的临时对象问题。但我的Matrix类还包含获得LU分解(高斯消除)的工具:

const Matrix& get_inverse();
const Matrix& get_l();
const Matrix& get_u();
const Matrix& get_p();

除了const之外的所有人都是,因为他们都打电话(如有必要):

void lupp();

更新缓存的L,U和P.同样代表get_inverse()调用lupp()并设置Matrix* Matrix::inverse。这会导致问题:

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

技术。