矩阵软件库是否应具有根类(例如MatrixBase
),其中有更多专用(或更多约束)矩阵类(例如SparseMatrix
,{{ 1}}等)派生?如果是这样,派生类应该公开/保护/私下派生吗?如果没有,它们是否应该用封装公共功能的实现类组成,否则不相关?还有别的吗?
我与软件开发人员同事(我本身并不是这样)就此进行了讨论,他们提到从更普遍的类中派生出一个更受限制的类是一个常见的编程设计错误(例如,他使用了这个例子)如何从UpperTriangluarMatrix
类派生Circle
类与矩阵设计问题相似并不是一个好主意,即使Ellipse
“是一个”SparseMatrix
{1}}。对于基本操作,基类和派生类所呈现的接口应该相同;对于特殊操作,派生类将具有可能无法为任意MatrixBase
对象实现的其他功能。例如,我们只能为MatrixBase
类对象计算cholesky分解;但是,乘以标量对于基类和派生类应该以相同的方式工作。此外,即使基础数据存储实现不同,PositiveDefiniteMatrix
也应该按预期工作,适用于任何类型的矩阵类。
我已经开始查看一些开源矩阵库,看起来这是一个混合包(或者我正在看一个混合的库包)。我正计划帮助重构一个数学库,这是一个争论的焦点,我想有意见(除非这个问题真的有一个客观的正确的答案至于什么样的设计理念是最好的,什么是任何合理方法的利弊。
答案 0 :(得分:4)
当您可以根据Ellipse界面修改一个维度时,会出现Ellipse的Circle子类(或Rectangle的Square子类)的问题,因此圆圈不再是圆形(并且方形不再是正方形)
如果您只允许不可修改的矩阵,那么您就是安全的,并且您可以自然地构建类型层次结构。
答案 1 :(得分:1)
呵呵呵。起初我读到你的朋友说圈子应该是一个椭圆,并写了一个很长的长篇大论,说明为什么它们充满了它。
你应该听听你的朋友,除了我希望他们不是说SparseMatrix“是一个”MatrixBase。这个术语在现实世界与建模世界中意味着不同的东西。在建模领域,“is-a”意味着遵循Liskov替换原则(查找它!)。或者,它意味着SparseMatrix必须遵循MatrixBase的约定,因为成员函数不需要任何额外的前提条件,并且必须满足不少后置条件。
我不知道这究竟是如何应用于矩阵问题的,但是如果你看看我在前一段(LSP和合同设计)中使用的术语,那么你应该很好地学习你的答案问题
在您的情况下可能适用的一种方法是在您的层次结构中采用各种共性并使它们成为抽象接口。然后继承那些正确响应它们的类中的接口。这将允许您编写应该允许常用的函数,但在变化太多的情况下仍保持分离。
答案 2 :(得分:1)
这是一个很好的问题,但我还不确定您要评估此指标的指标。
对于它的价值,我目前使用最多的一个Matrix库Armadillo确实有一个使用奇怪重复的remplate模式的公共Base
对象。我相信Eigen(另一个最近且经过严格模仿的矩阵库)会做同样的事情。
答案 3 :(得分:1)
要注意像这样的基于继承的设计的主要问题是SLICING。
假设MatrixBase定义了一个非虚拟赋值运算符。它复制所有矩阵子类共有的所有数据成员。您的SparseMatrix类定义了其他数据成员。现在,当我们写这个时会发生什么?
SparseMatrix sm(...);
MatrixBase& bm = sm;
bm = some_dense_matrix;
这段代码没什么意义(尝试直接通过基类中定义的运算符将DenseMatrix分配给SparseMatrix)并且容易出现各种令人讨厌的切片行为,但这是一个非常容易受到影响的方面,而且这个代码非常容易如果您通过MatrixBase * / MatrixBase&提供可访问的赋值运算符,那么这种情况很可能发生在某个地方。即使我们有这个:
SparseMatrix sm(...);
MatrixBase& bm = sm;
bm = some_other_sparase_matrix;
...由于赋值运算符是非虚拟的,我们仍然存在切片问题。如果没有公共基类的继承,我们可以提供赋值运算符来有意义地将密集矩阵复制到稀疏矩阵,但是尝试通过公共基类执行此操作会容易出现各种问题。
对于基类,通常应该避免使用赋值运算符!想象一下Dog和Cat从Mammal继承的情况,而Mammal提供了一个赋值运算符,虚拟或非虚拟。这意味着我们可以将狗分配给猫,这是没有意义的,即使操作员是虚拟的,也很难提供任何有意义的行为来将哺乳动物分配给其他哺乳动物。
假设我们尝试通过在Dog中实现赋值运算符来改善这种情况,这样它就只能分配给其他狗。现在当我们从Dog继承创建奇瓦瓦和杜宾犬时会发生什么?我们不应该将Chihuahuas分配给Dobermans,因此原始案例会递归地重复,直到您确定已经到达继承层次结构的叶节点(这是一种耻辱C ++没有最终关键字来阻止任何进一步继承)。
同样的问题很明显,常见的Circle继承了Ellipse的例子。圆圈可能需要宽度和高度来匹配:这是该类想要维护的不变量,但任何人都可以简单地获得指向Circle对象的基指针(Ellipse *)并违反该规则。
如果有疑问,请避免继承,因为这是C ++最常被滥用的功能之一,以及任何支持面向对象编程的语言。您可以尝试通过提供运行时机制来确定分配给另一个子类的子类的类型并仅允许匹配类型来解决该问题,但现在您正在进行大量额外工作并导致运行时开销。最好避免赋值运算符一起用于继承层次结构,并依赖于像clone这样的方法来生成副本(原型模式)。
因此,如果您选择从矩阵类中创建继承层次结构,则应仔细考虑继承的(最可能的短期)优势是否超过长期缺点。您还应该确保避免所有可能发生切片的情况,这对于矩阵库来说可能非常困难,而不会影响其可用性和效率。
答案 4 :(得分:0)
使用具有允许构建特定矩阵的方法的Matrix基类的可能性是否有用?例如(一个非常简单的例子):
MatrixClass m;
m.buildRotationMatrix(/*params*/)
// Now m is a rotation matrix
这在OpenSceneGraph框架中使用,适用于我们的目的。然而,构建方法只是旋转或反向等。但我觉得它可以让你避免导出许多矩阵子类的问题。
答案 5 :(得分:0)
如果有足够的通用方法和成员来保证基类,那么应该有一个和继承。我不会将基类用作所有矩阵的公共类型,而是作为常用方法和成员的容器(使构造函数受到保护)。
与Java不同,并非每个类或结构都需要基类。记住简单;复杂性使项目更长,更难以管理,更难以正确。