3

我正在参考我昨天写的这个问题来写这个问题。在一些文档之后,我似乎很清楚,我想做的事情(以及我认为可能的事情)即使不是不可能也几乎是不可能的。有几种方法可以实现它,由于我不是经验丰富的程序员,所以我问你会采取哪种选择。我再次解释了我的问题,但现在我有一些解决方案要探索。

我需要的

我有一个 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;

我今天拥有的

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

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

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

这种方法有一个问题:它不起作用。operator= 需要一个 Matrix& 并且 operator* 返回一个 Matrix。此外,由于另一个原因,这种方法对我来说看起来不太好:我正在处理可能非常大的矩阵,并且该库的目标是 1)对我的项目足够好 2)快速,所以可能通过按价值计算不应该是一种选择。

我探索了哪些解决方案

好吧,按照前面讨论中的建议,我阅读了一些关于智能指针的内容,它们看起来很棒,但我仍然无法弄清楚如何解决我的问题。它们处理内存释放和指针复制,但我基本上使用引用,所以它们看起来对我来说不是正确的选择。但我可能错了。

也许唯一的解决办法就是传值,也许我无法兼顾效率和良好的界面。但同样,你是专家,我想知道你的意见。

4

5 回答 5

9

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

Matrix& operator=(Matrix const& m);

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

你也应该为你做同样的事情operator*()

Matrix operator*(Matrix const& m) const;

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

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

于 2009-01-25T10:18:49.163 回答
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;
}

这样,用户可以在需要时进行乘法运算而无需创建新矩阵。当然,要使此代码正常工作,您还应该具有复制构造函数:)

于 2009-01-25T10:27:25.387 回答
3

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

由于矩阵可能是一个沉重的对象,你绝对应该避免深拷贝。智能指针是实现这一点的一种实用程序,但它们并不能立即解决您的问题。

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

使用Copy On Write,深拷贝会延迟到进行修改为止。例如,调用类似void Matrix.TransFormMe()on的成员函数b会看到实际数据被两个对象(a 和 b)引用,并在进行转换之前创建深层副本。

最终效果是您的矩阵类就像一个“普通”对象,但实际制作的深度副本数量大大减少了。

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

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

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

我曾经尝试在 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();
  }
}
于 2009-01-25T10:58:31.783 回答
3

......几乎都是 const 因为它们都调用(如有必要):

void lupp();

这会更新缓存的L,UP. 同样代表get_inverse()那个呼叫lupp(),也代表Matrix* Matrix::inverse. 这会导致以下问题:

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

技术。

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

于 2009-01-25T11:00:16.210 回答
0

是的,你的建议都很好,我承认我不知道非常量引用的临时对象问题。但是我的 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);

技术。

于 2009-01-25T10:51:18.303 回答