简体   繁体   English

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

[英]Coding practice: return by value or by reference in Matrix multiplication?

I'm writing this question with reference to this one which I wrote yesterday. 我正在写,参照这个问题这一个 ,我昨天写。 After a little documentation, it seems clear to me that what I wanted to do (and what I believed to be possible) is nearly impossible if not impossible at all. 经过一些文档记录后,我觉得我想做的事情(以及我认为可行的事情)几乎是不可能的,如果不是不可能的话。 There are several ways to implement it, and since I'm not an experienced programmer, I ask you which choice would you take. 有几种方法可以实现它,因为我不是一个经验丰富的程序员,我问你会选择哪种方式。 I explain again my problem, but now I have some solutions to explore. 我再次解释我的问题,但现在我有一些解决方案需要探索。

What I need 我需要的

I have a Matrix class, and I want to implement multiplication between matrices so that the class usage is very intuitive: 我有一个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;

What I had yesterday 我昨天有什么

At the moment I overloaded the two operators operator* and operator= and until yesterday night the were defined in this way: 目前我重载了两个运算符operator*operator= ,直到昨天晚上才以这种方式定义:

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

The operator* instantiates a new Matrix object ( Matrix return = new Matrix(...) ) on the heap, set the values and then just: operator *在堆上实例化一个新的Matrix对象( Matrix return = new Matrix(...) ),设置值然后只需:

return *result;

What I have today 我今天拥有的

After the discussion I decided to implement it in a "different way" to avoid the user to be bothered bother by pointers of any type and to keep the usage unchanged. 讨论之后,我决定以“不同的方式”实现它,以避免用户被任何类型的指针打扰并保持使用不变。 The "different way" is to pass the returning value of operator* by value: “不同的方式”是通过值传递operator *的返回值:

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

The operator* instantiates return on the stack, set the values and then return the object. operator *实例化堆栈上的return值,设置值然后返回对象。

There is a problem with this approach: it doesn't work. 这种方法存在问题:它不起作用。 The operator= expects a Matrix& and operator* returns a Matrix. operator =期望Matrix&和operator *返回一个Matrix。 Moreover this approach doesn't look so good to me for another reason: I'm dealing with matrices, that can be very large and the aims of this library were to be 1) good enough for my project 2) fast, so probably passing by value should not be an option. 此外,这种方法对我来说看起来并不那么好,原因是另一个原因:我正在处理矩阵,这可能非常大,而且这个库的目标是1)对我的项目来说足够好2)快,所以可能通过按价值不应该是一种选择。

Which solutions I have explored 我探讨了哪些解决方案

Well, following the suggestions in the previous discussion I read some stuff about smart pointers, they look great but I can't still figure out how to solve my problem with them. 好吧,按照前面讨论中的建议,我读了一些关于智能指针的内容,它们看起来很棒,但我仍然无法弄清楚如何用它们来解决我的问题。 They deal with memory freeing and pointer copying, but I'm basicly using references, so they don't look the right choice for me. 他们处理内存释放和指针复制,但我基本上使用引用,所以他们看起来不适合我。 But I may be wrong. 但我可能错了。

Maybe the only solution is to pass by value, maybe I can't get both efficiency and a good interface. 也许唯一的解决方案是传递价值,也许我无法获得效率和良好的界面。 But again, you're the expert, and I would like to know your opinion. 但同样,你是专家,我想知道你的意见。

The problem you are having is that the expression a * b creates a temporary object, and in C++, a temporary is not allowed to bind to a non-constant reference, which is what your Matrix& operator=(Matrix& m) takes. 您遇到的问题是表达式a * b创建一个临时对象,而在C ++中,不允许临时绑定到非常量引用,这是您的Matrix& operator=(Matrix& m)采用的。 If you change it to: 如果您将其更改为:

Matrix& operator=(Matrix const& m);

The code should now compile. 代码现在应该编译。 As well as the obvious benefit of producing compilable code :), adding the const also communicates to your callers that you will not be modifying the argument m , which may be helpful information. 除了生成可编译代码:)的明显好处之外,添加const还会向您的调用者传达您不会修改参数m ,这可能是有用的信息。

You should also do the same for your operator*() : 您也应该为您的operator*()做同样的事情:

Matrix operator*(Matrix const& m) const;

[EDIT: The additional const at the end indicates that the method promises not to alter *this , the object on the left-hand side of the multiplication, either. [编辑:最后的附加const表示该方法承诺不会改变*this ,即乘法左侧的对象。 This is necessary to cope with expressions such as a * b * c -- the subexpression a * b creates a temporary and won't bind without the const at the end. 这对于处理诸如a * b * c之类的表达式是必要的 - 子表达式a * b创建一个临时表达式,并且在最后没有const情况下不会绑定。 Thanks to Greg Rogers for pointing this out in the comments. 感谢格雷格罗杰斯在评论中指出这一点。 ] ]

PS The reason why C++ does not allow a temporary to bind to a non-constant reference is because temporaries exist (as the name suggests) for only a very short time, and in most cases, it would be a mistake to attempt to modify them. PS C ++不允许临时绑定到非常量引用的原因是因为临时存在(顾名思义)只有很短的时间,并且在大多数情况下,尝试修改它们是错误的。

You should really read Effective C++ by Scott Meyers, it has great topics on that. 您应该阅读Scott Meyers的Effective C ++ ,它有很多主题。 As already said, the best signatures for operator= and operator* are 如前所述, operator=operator*的最佳签名是

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

but I have to say you should implement multiplication code in 但我不得不说你应该实现乘法代码

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

and just reuse it in operator* 并在operator*重复使用operator*

Matrix operator*(Matrix const &m) const {
    return Matrix(*this) *= m;
}

that way user could multiply without creating new matrices when she wants to. 这样,用户可以在不想创建新矩阵的情况下成倍增加。 Of course for this code to work you also should have copy constructor :) 当然这个代码工作你也应该有复制构造函数:)

Note: Start with Vadims suggestions. 注意:从Vadims建议开始。 The following discussion is moot if we talk about very small matrices only, eg if you limit yourself to 3x3 or 4x4 matrices. 如果我们仅讨论非常小的矩阵,则以下讨论没有实际意义,例如,如果您将自己限制为3x3或4x4矩阵。 Also I hope I am not trying to cram to much ideas down on you :) 另外,我希望我不是想把很多想法塞进你身边:)

As a matrix is potentially a heavy object, you should definitely avoid the deep copies. 由于矩阵可能是一个重物,你绝对应该避免使用深拷贝。 Smart pointers are one utility to implement that, but they don't solve your problem out of the box. 智能指针是实现它的一个实用程序 ,但它们并不能解决您的问题。

In your scenario, there are two common approaches. 在您的方案中,有两种常见方法。 In both cases, any copy (like a=b ), only copies a reference, and increments the reference counter (that's what smart pointers can do for you). 在这两种情况下,任何副本(如a=b ),只复制引用,并递增引用计数器(这是智能指针可以为您做的)。

With Copy On Write , the deep copy is delayed until a modification is made. 使用Copy On Write时 ,深度复制将延迟,直到进行修改。 eg calling a member function like void Matrix.TransFormMe() on b would see that the actual data is referenced by two objects (a and b), and create a deep copy before doing the transformation. 例如,在b上调用像void Matrix.TransFormMe()这样的成员函数会看到实际数据被两个对象(a和b)引用,并在进行转换之前创建一个深层副本。

The net effect is that your matrix class acts like a "normal" object, but the number of deep copies actually made is reduced dramatically. 实际效果是你的矩阵类就像一个“普通”对象,但实际制作的深拷贝数量会大大减少。

The other approach are Immutable Objects where the API itself never modifies an existing object - any modification creates a new object. 另一种方法是Immutable Objects ,其中API本身永远不会修改现有对象 - 任何修改都会创建一个新对象。 So instead of a void TransformMe()' member transforming the contained matrix, Matrix contains only a Matrix GetTransformed()`member, returning a copy of the data. 因此void TransformMe()' member transforming the contained matrix, Matrix contains only avoid TransformMe()' member transforming the contained matrix, Matrix contains only a而是void TransformMe()' member transforming the contained matrix, Matrix contains only a Matrix GetTransformed()`成员,返回数据的副本。

Which method is better depends on the actual data. 哪种方法更好取决于实际数据。 In MFC, CString is copy-on-write, in .NET a String is immutable. 在MFC中, CString是写时复制,在.NET中, String是不可变的。 Immutable classes often need a builder class (like StringBuilder ) that avoids the copies of many sequential modifications. 不可变类通常需要一个构建器类(如StringBuilder ),它避免了许多顺序修改的副本。 Copy-On-Write objects need careful design so that in the API it is clear which member modify the internal members, and which return a copy. Copy-On-Write对象需要仔细设计,以便在API中清楚哪个成员修改内部成员,哪些成员返回副本。

For matrices, since there are many algorithms that can modify the matrix in-place (ie the algorithm itself does not need the copy), copy-on-write might be the better solution. 对于矩阵,由于有许多算法可以就地修改矩阵(即算法本身不需要复制),因此写时复制可能是更好的解决方案。

I've once tried to build a copy-on-write pointer on top of boost smart pointers, but I haven't touched it to figure out threading issues etc. Pseudocode would look like this: 我曾经尝试在boost智能指针之上构建一个写时复制指针,但我没有触及它来找出线程问题等。伪代码看起来像这样:

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

… Are all but const since they all call (if necessary): ......除了const之外,所有人都打电话给他们(如有必要):

 void lupp(); 

That updates the cached L , U and P . 这会更新缓存的LUP The same stands for get_inverse() that call lupp() and also sets Matrix* Matrix::inverse . 同样代表get_inverse()调用lupp()并设置Matrix* Matrix::inverse This causes problem with the: 这会导致以下问题:

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

technique. 技术。

Please explain how that causes problems. 请解释这是如何导致问题的。 It should't, usually. 通常不应该这样。 Additionally, if you use member variables to cache temporary results, make them mutable . 此外,如果使用成员变量来缓存临时结果,请使它们mutable Then you can modify them even in const objects. 然后你甚至可以在const对象中修改它们。

Yes, your suggestion are both good, and I admit that I didn't know the temporary object issue with non-const references. 是的,你的建议都很好,我承认我不知道非const引用的临时对象问题。 But my Matrix class also contains facilities to get the LU factorization (Gaussian Elimination): 但我的Matrix类还包含获得LU分解(高斯消除)的工具:

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

Are all but const since they all call (if necessary): 因为他们都打电话(如有必要),所有人都是const

void lupp();

That updates the cached L, U and P. The same stands for get_inverse() that call lupp() and also sets Matrix* Matrix::inverse . 这会更新缓存的L,U和P.同样代表get_inverse()调用lupp()并设置Matrix* Matrix::inverse This causes problem with the: 这会导致以下问题:

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

technique. 技术。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM