2010-05-03 78 views
9

如果一个矩阵软件库有一个根类(例如,MatrixBase),从该多个专门(或更受约束)矩阵的类(例如,SparseMatrixUpperTriangluarMatrix等)推导出?如果是这样,派生类应该公开/保护性/私人性派生吗?如果不是,它们是否应该与封装了通用功能的实现类组合,否则无关联?还有别的吗?C++矩阵类层次结构

我和一位软件开发人员(我本人并不是这样)谈过这件事,他提到从一个更普通的类派生更受限制的类是一种常见的编程设计错误(例如,他使用了例如,如何从Ellipse类中推导类似于矩阵设计问题的Circle类并不是一个好主意的例子),即使它确实是一个“真实”“IS A”MatrixBase。基本操作和派生类所呈现的接口对于基本操作应该是相同的;对于专门的操作,派生类将具有可能无法实现的任意MatrixBase对象的附加功能。例如,我们可以只为PositiveDefiniteMatrix类对象计算cholesky分解;然而,乘以标量应该对基类和派生类都以相同的方式工作。此外,即使底层数据存储实现不同,operator()(int,int)应该可以像任何类型的矩阵类一样按预期工作。

我已经开始寻找一些开源矩阵库,看起来像这是一种混合包(或者我正在寻找一堆混合的库)。我计划帮助重构一个数学库,这是一个争论的焦点,我希望有意见(这是除非真的有客观的权利回答这个问题)关于什么设计哲学将是最好的,任何合理的方法有什么优点和缺点。

回答

0

具有Matrix基类的可能性是否有可用于构建特定矩阵的方法?例如(一个非常简单的例子):

MatrixClass m; 
m.buildRotationMatrix(/*params*/) 
// Now m is a rotation matrix 

这是在OpenSceneGraph框架中使用,适用于我们的目的。但是,构建方法只是旋转或反转等。但我觉得它会让你避免导出许多矩阵子类的问题。

4

当您可以修改每个椭圆接口的一个尺寸时,发生椭圆(或矩形的方形子类)的Circle子类的问题,因此该圆不再是圆形(并且方形不再是方形)。

如果你只允许不可修改的矩阵,那么你是安全的,你可以自然地构造你的类型层次结构。

+1

+1对于“仅当观察时圆是椭圆”。 – avakar 2010-05-03 16:15:48

+0

这是使用不可变数据的“投票”吗? – bpw1621 2010-05-03 17:28:20

+0

是的,我更喜欢不可变的数据,尽管我知道这在C++中并不常见。 – starblue 2010-05-03 20:32:42

1

hehehe。起初,我读到你的朋友说圈子应该是椭圆形,并写了一个长篇大论为什么他们充满了它。

你应该听你的朋友,除了我希望他们不是说SparseMatrix“是 - ”MatrixBase。这个术语在现实世界与建模世界中意味着不同的东西。在建模世界中,“is-a”意味着遵循Liskov替换原则(查看它!)。或者,它意味着SparseMatrix必须遵循MatrixBase的合同,因为成员函数不需要任何额外的先决条件,并且必须满足不少于后置条件。

我不知道这是如何适用于矩阵问题,但如果你看一下前面段落中使用的术语(LSP和Design by Contract),那么你应该很好地学习答案你的问题。

您的情况可能适用的一种方法是在您的层次结构中采用各种通用性,并使其成为抽象接口。然后从这些接口中继承正确响应它们的类。这将允许你编写应该允许通用的函数,但在变化太多的情况下仍然保持分离。

+0

感谢参考指南。没有意识到有一个关于这一点的整个维基百科文章:http://en.wikipedia.org/wiki/Circle-ellipse_problem – bpw1621 2010-05-03 17:27:00

1

这是一个很好的问题,但我还不确定你想要评估的指标是什么。

对于什么是值得的,我目前使用的一个矩阵库最多的是Armadillo不使用好奇地重复remplate模式有一个共同的Base对象。我相信Eigen(另一个最近和模板严重的矩阵库)也是如此。

+0

度量将是任何通常的软件一般(正确性,效率,可维护性等等),并且没有额外的信息来提供您想要以矩阵库自己排列它们的顺序。 – bpw1621 2010-05-03 16:54:36

+0

那么CRTP如何帮助到这里呢?它不是一个有效的向下模式(它通常被称为SO上的代码味道)?例如, template class MatrixBase {void interface(){static_cast (this) - > implementation(); }}; UpperTriangularMatrix:MatrixBase {void implementation(); }; 因此,如果接口和实现引用矩阵乘法,接口调用委托给派生类中的正确实现调用的想法是什么?用法,客户端只调用接口? – bpw1621 2010-05-03 17:08:16

+0

无论如何格式化评论就像有帖子?反驳对上述评论不起作用。 – bpw1621 2010-05-03 17:10:12

0

如果有足够的通用方法和成员来保证基类,那么应该有一个继承。我不会使用基类作为所有矩阵的通用类型,而是作为常用方法和成员的容器(使构造函数受保护)。

与Java不同,并不是每个类或结构都需要一个基类。记住简单;复杂性使得项目变得更长,更难以管理并且更难以得到正确的结果。

1

要注意这种基于继承的设计的主要问题是切片。

比方说,MatrixBase定义了一个非虚拟的赋值运算符。它复制所有矩阵子类通用的所有数据成员。您的SparseMatrix类定义了额外的数据成员。现在当我们写这个时会发生什么?

SparseMatrix sm(...); 
MatrixBase& bm = sm; 
bm = some_dense_matrix; 

此代码意义不大(试图直接通过在基类中定义的操作者指定一个DenseMatrix到稀疏矩阵),并且是容易出现各种讨厌切片的行为,但是这样的代码的一个脆弱方面如果您通过MatrixBase */MatrixBase &提供赋值运算符,那么就有可能发生这种情况。即使我们有这样的:

SparseMatrix sm(...); 
MatrixBase& bm = sm; 
bm = some_other_sparase_matrix; 

...我们还是由于赋值运算符是非虚拟切片问题。如果没有公共基类的继承,我们可以提供赋值运算符来有意义地将密集矩阵复制到稀疏矩阵,但是通过共同基类尝试执行此操作会容易出现各种问题。

对于基类,通常应避免赋值运算符!想象一下,狗和猫从哺乳动物和哺乳动物中继承的情况提供了一个虚拟或不虚拟的赋值操作符。这意味着我们可以将狗分配给猫,这是毫无意义的,即使操作员是虚拟的,也很难提供任何有意义的行为来将哺乳动物分配给其他哺乳动物。

假设我们试图通过在Dog中实现赋值运算符来改善这种情况,以便它只能被分配给其他的狗。现在当我们继承Dog来创造奇瓦瓦狗和杜宾犬时会发生什么?我们不应该能够将奇瓦瓦州分配给Dobermans,因此原始案例会递归地重复,直到您确定您已经到达继承层次结构的叶节点为止(这是一种耻辱C++没有最终关键字来阻止任何进一步继承)。

同样的问题显然与普通的Circle继承椭圆的例子。圆圈可能需要宽度和高度匹配:这是类不变的类,但任何人都可以简单地获得指向Circle对象的基指针(Ellipse *)并违反该规则。

如果有疑问,请避免继承,因为这是C++和任何通常支持面向对象编程的语言中最被滥用的特性之一。您可以尝试通过提供运行时机制来确定分配给其他子类的子类的类型,并只允许匹配类型,但现在您正在执行大量额外工作并导致运行时开销。最好避免赋值运算符一起用于继承层次结构,并依赖像克隆这样的方法来产生副本(原型模式)。因此,如果您选择在矩阵类中创建继承层次结构,则应仔细考虑继承的(最可能的短期)优势是否超过了长期劣势。您还应该确保避免可能发生切片的所有潜在情况,这对矩阵库可能非常困难,而且不会影响其可用性和效率。