2009-01-25 8 views
3

我写了这个问题,参考我昨天写的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); 

运算符*实例堆上新的Matrix对象(Matrix return = new Matrix(...)),设置的值,然后只是:

return *result; 

我今天

有什么后discussion我决定以“不同的方式”实现它,以避免用户被任何类型的指针困扰,并保持用法不变。所述“不同的方式”是通过值传递运算符*的返回值:

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

运算符*实例return在堆栈上,设置的值,然后返回该对象。

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

我已经探索了哪些解决方案

那么,按照前面discussion的建议,我读到的智能指针一些东西,它们看起来很棒,但我仍无法弄清楚如何解决我的问题与他们。他们处理内存释放和指针复制,但我基本上使用引用,所以他们不看我正确的选择。但我可能是错的。

也许唯一的解决办法是通过价值传递,也许我不能同时获得效率和良好的界面。但是,你是专家,我想知道你的意见。

回答

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++不允许临时绑定到非常量引用的原因是因为临时存在(正如名称所示)只有很短的时间,并且在大多数情况下,尝试修改它们是错误的。

+0

我同意这一点 - 尽可能多地使用const,然后依靠RVO(返回值优化)之类的内容来避免尽可能多的无关副本。 当然,如果你需要更好的性能,你可以看看peterchen提到的写时复制语义。 – 2009-01-25 11:05:46

+1

第二个片段应该是矩阵运算符*(Matrix const&m)const;实际上如果你打算把它作为一个具有const正确性的成员操作符来实现。 – 2009-01-26 17:52:50

+0

你是对的格雷格。我编辑了这篇文章以反映这一点。 – 2009-01-26 21:31:28

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

用户可以在不需要创建新矩阵的情况下进行繁殖。 当然,为了这个代码的工作,你也应该有复制构造函数:)

+0

+1,都是很好的建议。 (尽管实际上,除非OP在Matrix内部进行动态内存分配,否则最好不要*运算符=()也不要使用默认值)并且只使用默认值。)只是一件小事,我认为你需要一个“* “在你的最后一段代码片段中的”this“之前。 – 2009-01-25 10:33:41

+0

感谢您的纠正,这是我的愚蠢。 – vava 2009-01-25 10:39:22

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.同样代表呼叫lupp()的get_inverse(),并且还设置Matrix* Matrix::inverse。这会导致以下问题:

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

技术。

3

注意:从Vadims的建议开始。如果我们只讨论非常小的矩阵,下面的讨论是没有意义的,例如,如果您将自己限制为3x3或4x4矩阵。另外,我希望我不是想让你陷入太多的想法:)

作为一个矩阵可能是一个沉重的对象,你应该避免深度复制。智能指针是一个实用程序来实现,但它们并不能解决您的问题。

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

With 复制写入,深度复制被延迟,直到做出修改。例如调用类似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),以避免许多顺序修改的副本。写入时复制对象需要仔细设计,以便在API中明确哪些成员修改了内部成员,哪些成员返回了副本。

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

我曾经试图建立升压智能指针顶部的副本上写指针,但我还没有碰过它弄清楚线程的问题等伪代码是这样的:

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

...是不是所有,但常量,因为它们都调用(如有必要):

void lupp(); 

的更新缓存LUP。同样代表get_inverse(),它调用lupp()并且还设置Matrix* Matrix::inverse。这会导致以下问题:

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

技术。

请解释如何导致问题。它通常不应该。此外,如果您使用成员变量来缓存临时结果,请将它们设为mutable。然后即使在const对象中也可以修改它们。