2010-05-03 74 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++和任何通常支持面向對象編程的語言中最被濫用的特性之一。您可以嘗試通過提供運行時機制來確定分配給其他子類的子類的類型,並只允許匹配類型,但現在您正在執行大量額外工作並導致運行時開銷。最好避免賦值運算符一起用於繼承層次結構,並依賴像克隆這樣的方法來產生副本(原型模式)。因此,如果您選擇在矩陣類中創建繼承層次結構,則應仔細考慮繼承的(最可能的短期)優勢是否超過了長期劣勢。您還應該確保避免可能發生切片的所有潛在情況,這對矩陣庫可能非常困難,而且不會影響其可用性和效率。