2012-12-17 15 views
7

boost library documentation我這樣說的:如何使用智能指針將對象複製爲類屬性?

概念上,智能指針被看作是擁有指向的對象, 因而負責對象的刪除時不再需要 。

我有一個非常簡單的問題:我想使用RAII作爲可複製和可分配的類的指針屬性。

複製和分配操作應該很深:每個對象都應該有自己的實際數據副本。此外,RTTI需要可用於屬性(它們的類型也可以在運行時確定)。

我應該尋找能夠複製的智能指針的實現(數據很小,所以我不需要Copy on Write指針),或者我委託複製操作我的對象的拷貝構造函數如圖this answer

對於可複製和賦值的類的簡單RAII,我選擇哪一個智能指針? (我想,隨着委託複製/賦值操作類的拷貝構造函數和賦值運算符的unique_ptr會作出正確的選擇,但我不知道)

下面是使用原始指針的問題,它只是一個問題的描述,而不是運行C++代碼:

// Operation interface 
class ModelOperation 
{ 
    public: 
     virtual void operate =(); 
}; 

// Implementation of an operation called Special 
class SpecialModelOperation 
: 
    public ModelOperation 
{ 
    private: 
     // Private attributes are present here in a real implementation. 

    public: 

     // Implement operation 
     void operate() {}; 
}; 

// All operations conform to ModelOperation interface 
// These are possible operation names: 
// class MoreSpecialOperation; 
// class DifferentOperation; 

// Concrete model with different operations 
class MyModel 
{ 
    private: 
     ModelOperation* firstOperation_; 
     ModelOperation* secondOperation_; 

    public: 

     MyModel() 
      : 
       firstOperation_(0), 
       secondOperation_(0) 
     { 
      // Forgetting about run-time type definition from input files here. 
      firstOperation_ = new MoreSpecialOperation(); 
      secondOperation_ = new DifferentOperation(); 
     } 

     void operate() 
     { 
      firstOperation_->operate(); 
      secondOperation_->operate(); 
     } 

     ~MyModel() 
     { 
      delete firstOperation_; 
      firstOperation_ = 0; 

      delete secondOperation_; 
      secondOperation_ = 0; 
     } 
}; 

int main() 
{ 

    MyModel modelOne; 

    // Some internal scope 
    { 
     // I want modelTwo to have its own set of copied, not referenced 
     // operations, and at the same time I need RAII to for the operations, 
     // deleting them automatically as soon as it goes out of scope. 
     // This saves me from writing destructors for different concrete models. 
     MyModel modelTwo (modelOne); 
    } 


    return 0; 
} 
+0

如果您閱讀[shared_ptr]的描述(http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm),您將看到它們是CopyConstructible和Assignable。 –

+1

@Joachim是的,他們分享對同一個對象的引用。我需要複製操作來執行深層複製。 – tmaric

+0

沒有使用任何智能指針的選項? –

回答

5

如果您接受對您的類型的某些要求,則無需爲所有類型實施虛擬克隆功能即可完成此操作。特殊的要求是這些類型具有可訪問的拷貝構造函數,有些人會認爲這是不可取的,因爲可能發生意外切片。然而,正確使用朋友關係可能會緩解這種缺陷。

如果這樣是可以接受的一個可以通過提供複製功能的接口下擦除派生類型去對此:

template <typename Base> 
struct clonable { 
    // virtual copy 
    // this clone function will be generated via templates 
    // no boilerplate is involved 
    virtual std::unique_ptr<clonable<Base>> clone() const = 0; 

    // expose the actual data 
    virtual Base* get() = 0; 
    virtual Base const* get() const = 0; 

    // virtual destructor 
    // note that this also obviates the need for a virtual destructor on Base 
    // I would probably still make it virtual, though, just in case 
    virtual ~clonable() = default; 
}; 

該接口由模板上最派生類型一類中實現,並且因此知道如何通過複製構造函數來製作正常副本。

template <typename Base, typename Derived> 
struct clonable_holder : clonable<Base> { 
    // I suppose other constructors could be provided 
    // like a forwarding one for emplacing, but I am going for minimal here 
    clonable_holder(Derived value) 
    : storage(std::move(value)) {} 

    // here we know the most derived type, so we can use the copy constructor 
    // without risk of slicing 
    std::unique_ptr<clonable<Base>> clone() const override { 
     return std::unique_ptr<clonable<Base>>(new clonable_holder(storage)); 
    } 

    Base* get() override { return &storage; } 
    Base const* get() const override { return &storage; } 

private: 
    Derived storage; 
}; 

這將爲我們生成虛擬複製功能,無需額外的樣板。現在我們可以在此基礎上構建一個類似於智能指針的類(不是一個智能指針,因爲它不提供指針語義,而是賦值語義)。

template <typename Base> 
struct polymorphic_value { 
    // this constructor captures the most derived type and erases it 
    // this is a point where slicing may still occur 
    // so making it explicit may be desirable 
    // we could force constructions through a forwarding factory class for extra safety 
    template <typename Derived> 
    polymorphic_value(Derived value) 
    : handle(new clonable_holder<Base, Derived>(std::move(value))) { 
     static_assert(std::is_base_of<Base, Derived>::value, 
      "value must be derived from Base"); 
    } 

    // moving is free thanks to unique_ptr 
    polymorphic_value(polymorphic_value&&) = default; 
    polymorphic_value& operator=(polymorphic_value&&) = default; 

    // copying uses our virtual interface 
    polymorphic_value(polymorphic_value const& that) 
    : handle(that.handle->clone()) {} 
    polymorphic_value& operator=(polymorphic_value const& that) { 
     handle = that.handle->clone(); 
     return *this; 
    } 

    // other useful constructors involve upcasting and so on 

    // and then useful stuff for actually using the value 
    Base* operator->() { return handle.get(); } 
    Base const* operator->() const { return handle.get(); } 
    // ... 

private: 
    std::unique_ptr<clonable<Base>> handle; 
}; 

這僅僅是一個最小的接口,但它可以很容易地從這裏充實,以覆蓋更多的使用場景。

+0

絕對是一個詳細的答案,謝謝!然而,這看起來比包裝一個指針複雜併爲其定義複雜得多。這可能是我缺乏經驗......但這似乎是一個相當複雜的方法來解決這個問題...... – tmaric

+0

你如何在你的「包裝一個指針,併爲其定義副本和分配」方法中製作副本?向'ModelOperation'接口添加一個'clone'函數並在每個派生類中手動實現它?請注意,此代碼中的所有代碼都要寫入一次並重用,而不是每個派生類中的一次。 –

+0

另請注意,您的示例不是異常安全的:如果'secondOperation_ = new DifferentOperation();'會發生什麼?如果構造函數沒有完成,那麼不會調用MyModel的析構函數,所以在前一行創建的'MoreSpecialOperation'只會泄漏。使用類似智能指針的類將使其自動安全。所有這些沒有任何樣板:你的例子變成了'class MyModel {polymorphic_value firstOperation; polymorphic_value secondOperation;/*沒有複製ctors,也沒有賦值ops,也沒有析構函數寫* /};' –

1

聽起來好像需要能夠使創建的新副本智能指針對象每次創建另一個智能指針對象時。 (無論這個副本是否「深」,取決於對象的構造函數,我猜;你所存儲的對象可能有很多深層所有權的級別,因爲我們所知道的,所以「深」取決於我們的目的主要是,當智能指針由另一個引用構造而不是僅僅取出指向現有對象的指針時,您需要創建獨特對象的東西。)

如果我已經正確地理解了這個問題,那麼你將需要一個虛擬克隆方法。沒有其他方法可以正確調用派生類的構造函數。

struct Clonable { 
    virtual ~Clonable() {} 
    virtual Clonable* clone() = 0; 
}; 
struct AutoPtrClonable { 
    AutoPtrClonable(Clonable* cl=0) : obj(cl) { } 
    AutoPtrClonable(const AutoPtrClonable& apc) : obj(apc.obj->clone()) { } 
    ~AutoPtrClonable() { delete obj; } 
    // operator->, operator*, etc 
    Clonable* obj; 
}; 

要使用示例代碼,使之成爲一個模板等

+0

我正在考慮完全放棄智能指針。在涉及智能指針的所有權基礎上的RAII與在std ::或boost :: ::中沒有可複製的智能指針的情況之間似乎存在衝突,而且沒有所有權轉移或引用的使用。 – tmaric

+1

@tomislav你必須擁有所有權或使用引用的轉移,除非你有一個特殊的接口(如Clonable):通過指針給出一個對象,例如複製構造函數的普通調用不能複製該對象。編譯器如何知道調用哪個對象的構造函數 - 派生最多?這需要虛擬查找,而在C++中,您必須編寫自己的clone()來獲取它。 –

+1

如果你可以忍受你的類型的一些要求,你可以做到這一點,沒有克隆功能,只需使用常規的拷貝構造函數。該解決方案涉及通過模板在外部自動生成克隆功能。 (我會看看我是否可以在我有空的時候儘快寫出答案;可惜我目前的項目編譯速度非常快) –

0

有兩種解決方法(其實你有更多的人,但這些都使得最有意義的我:)):

首先,您可以使用std::unique_ptr。這是一個很好的解決方案,因爲它強制每個指針有一個實例。(使用std::shared_ptr也可以,但是如果不明確地添加代碼,複製和分配shared_ptr將「共享」 - 尤其是您想要避免的)。

如果使用std::unique_ptr,你的拷貝構造函數和賦值運算符應明確深拷貝(使用的指針對象的接口的虛擬clone方法,或新的運營商在調用構造函數unique_ptr)。

第二,你可以推出自己的。沒有什麼複雜的,我們正在談論一個小的(10-20行左右)實用程序。

就個人而言,如果我不得不在一個地方使用這個智能指針類,我會使用std :: unique_ptr。否則(多指針,相同的行爲)我會推出我自己的,只是這樣我就不必爲許多實例重複深層複製(保持DRY原則)。

4

這有點晚了,但對於未來的觀衆:我的僅頭標庫Aurora及其SmartPtr tutorial中有一個現成的實現。藉助Aurora,通過智能指針實現深度複製是微不足道的。下面的代碼適用於任何複製的類型T

aurora::CopiedPtr<T> first(new T); 
aurora::CopiedPtr<T> second = first; // deep copy 

這使得通常沒有必要去實施三大/五,如果你的類具有指針成員。

+3

永遠不會太晚。我讀了你的回答,發現你的圖書館正是我所期待的。謝謝! –