我正在写这个问题,参考我昨天写的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中的建议,我读了一些关于智能指针的内容,它们看起来很棒,但我仍然无法弄清楚如何用它们来解决我的问题。他们处理内存释放和指针复制,但我基本上使用引用,所以他们看起来不适合我。但我可能错了。
也许唯一的解决方案就是传递价值,也许我无法获得效率和良好的界面。但同样,你是专家,我想知道你的意见。
答案 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
乘法的em>。这对于处理诸如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();
更新缓存的
L
,U
和P
。同样代表调用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);
技术。