2010-07-28 102 views
1

考慮下面的類結構:如何處理指針成員的不同所有權策略?

class Filter 
{ 
    virtual void filter() = 0; 
    virtual ~Filter() { } 
}; 

class FilterChain : public Filter 
{ 
    FilterChain(collection<Filter*> filters) 
    { 
     // copies "filters" to some internal list 
     // (the pointers are copied, not the filters themselves) 
    } 

    ~FilterChain() 
    { 
     // What do I do here? 
    } 

    void filter() 
    { 
     // execute filters in sequence 
    } 
}; 

我露出類圖書館,所以我沒有在它如何被使用的控制。

我目前有關於Filter對象的所有權的一些設計問題FilterChain是持有指針。更具體地講,這裏有兩種可能的使用情景FilterChain

  • 場景A:一些在我的庫中的函數構造(儘可能複雜)的過濾鏈,內存需要分配,並返回一個新分配FilterChain目的。例如,其中一個函數從文件構造一個過濾器鏈,該文件可以描述任意複雜的過濾器(包括過濾器鏈的過濾器鏈等)。一旦工作完成,函數的用戶負責對象的銷燬。
  • 情景B:用戶可以訪問一堆Filter對象,並且希望以特定的方式將它們結合到過濾器鏈中。用戶構建自己使用的對象,然後在完成對象時銷燬它們。當引用它們的FilterChain被銷燬時,Filter對象一定不會被銷燬被銷燬。

現在,來管理FilterChain對象所有權的兩種最簡單的方法是:

  • FilterChain擁有Filter對象。這意味着FilterChain引用的對象在FilterChain的析構函數中被銷燬。這與場景B不兼容。
  • FilterChain確實不是自己的Filter對象。這意味着FilterChain的析構函數什麼都不做。現在情景A存在問題,因爲用戶必須知道涉及的所有對象的內部結構,以便全部銷燬它們而不會丟失所有對象,因爲父親FilterChain本身不會執行它。這只是不好的設計,並要求內存泄漏。

因此,我需要更復雜的東西。我的第一個猜測是設計一個帶有可設置布爾標誌的智能指針,指示智能指針是否擁有該對象。然後,而不是採取指向Filter對象的指針的集合,FilterChain將採取指向Filter對象的智能指針的集合。當調用FilterChain的析構函數時,會破壞智能指針。當且僅當指示所有權的布爾標誌被設置時,智能指針本身的析構函數纔會銷燬指向的對象(一個Filter對象)

我感覺這個問題在C++中很常見,但是我的網頁搜索流行的解決方案或者聰明的設計模式並不是很成功。實際上,auto_ptr在這裏並沒有真正的幫助,shared_ptr似乎矯枉過正。那麼,我的解決方案是不是一個好主意?

+4

爲什麼共享指針過度殺傷?在即時實施和維護(每個人都確切地知道它們是什麼)方面比自己更加容易。開銷很小。 – Patrick 2010-07-28 19:12:05

+2

基類析構函數必須是虛擬的。 – 2010-07-28 19:14:29

+0

@Patrick:對我來說感覺像是矯枉過正,因爲shared_ptr是一個引用計數器,我只需要一個布爾標誌。 – 2010-07-28 19:30:32

回答

2

這裏的智能指針並不過分:顯然你有一個設計問題,這種或那種方式需要仔細考慮對象的生命週期和所有權。如果您希望在運行時重新修補過濾器圖形中的過濾器,或者可以創建複合對象,這尤其如此。

使用shared_ptr將一舉消除大部分問題,讓您的設計變得更簡單。我認爲這裏唯一潛在的問題是如果你的過濾器碰巧包含循環。我可以看到,如果你有某種反饋循環,可能會發生。在這種情況下,我建議讓所有的對象擁有一個類,然後FilterChain將存儲指向Filter對象的弱指針。

我敢打賭,過濾器階段的執行時間將遠遠超過解引用智能指針的額外開銷。 shared_ptr被設計成相當輕巧。

+0

似乎每個人都認爲這是正確的事情。好吧,我們來看看shared_ptr。 – 2010-07-29 07:18:06

+0

我以前在智能指針方面有一點心理障礙,但最近開始使用shared_ptr,我發現它使得生活更容易。 – 2010-07-29 08:04:47

0

FilterChain應該有一個單獨的DeleteAll()方法,該方法通過集合的迭代器和delete的過濾器。它在場景A中調用,而不是在場景B中調用。這確實需要一些關於FilterChain用戶的智能,但不會再記住delete,並且他們的對象是他們new'd。 (他們應該能夠處理,或者他們應該有一個memeory泄漏)

+2

他們不應該*要*記得在任何地方刪除任何東西。使用容器,智能指針等。保持異常安全,乾淨,從不明確管理工具類以外的事物。 – GManNickG 2010-07-28 19:22:20

2

過濾器是如此之大,你不能簡單地讓每個人的深層副本在創建FilterChain?如果你能夠做到這一點,那麼你所有的問題都會消失:FilterChain總是會自行清理。

如果由於內存問題,這不是一個選項,那麼使用shared_ptr似乎是最有意義的。調用者必須負責爲其關心的每個對象保留shared_ptr,然後當delete d時,FilterChain將知道是否刪除特定的過濾器。

編輯:正如尼爾指出的Filter需要一個虛擬析構函數。

+0

過濾器對象是不可複製的,不是因爲內存問題,而是因爲它會導致與狀態信息重複相關的問題(同樣,它在語義上也是可疑的)。 – 2010-07-28 19:27:39

+0

@ e-t172那麼如何將過濾器狀態存儲在'shared_ptr'中,以便您可以複製'Filter'? – 2010-07-28 19:57:31

+0

在FilterChain中使用shared_ptr有什麼區別? – 2010-07-28 20:05:38

0

我會去與FilterChain不擁有過濾器對象。然後,在你的庫中,當你需要從文件中加載一個FilterChain時,你會有另一個負責Filter對象生命週期的Loader對象。所以,FilterChain會一直爲圖書館和用戶創建的Chains加載的Chains工作。

+0

這意味着將可能複雜的過濾器結構複製到Loader對象中,僅用於管理所有權。與其他解決方案相比,這太複雜了。 – 2010-07-28 21:21:13

+0

實際上,您提出的只是我原始問題中的第二個「解決方案」,不同之處在於您將管理濾鏡對象的邏輯從庫外移動到內部。事實上,對於圖書館用戶而言,代碼更簡單,但僅僅因爲圖書館內部隱藏了過度的複雜性並不意味着它是好的設計(這是必要的但並不充分)。 – 2010-07-28 21:24:47

+0

我看不出爲什麼你需要在裝載器中複製過濾器結構。加載器只需要管理過濾器的生命週期。庫的用戶仍然需要管理他們自己的過濾器的生命週期。過濾器應該(de)序列化。每當我看到這樣的代碼,這是因爲沒有足夠的思想被放入Filter類的設計中,現在你付出了代價。 – 2010-07-28 21:43:03