How to approach copying objects with smart pointers as class attributes?

后端 未结 5 594
小鲜肉
小鲜肉 2021-01-02 16:15

From the boost library documentation I read this:

Conceptually, smart pointers are seen as owning the object pointed to, and thus responsible for de

相关标签:
5条回答
  • 2021-01-02 16:18

    It sounds like need to be able to make a smart pointer that creates a new copy of the object each time another smart pointer object is created. (Whether that copy is "deep" or not is up to the constructor of the object, I guess; the objects you're storing could have many levels deep of ownership, for all we know, so "deep" depends on the meaning of the objects. The main thing for our purposes is that you want something that creates a distinct object when the smart pointer is constructed with a reference from another one, rather than just taking out a pointer to the existing object.)

    If I've understood the question correctly, then you will require a virtual clone method. There's no other way to call the derived class's constructor correctly.

    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;
    };
    

    To use sample code, make it into a template, etc.

    0 讨论(0)
  • 2021-01-02 16:20

    I've never heard about ready-to-use realisation, but you can simply do it by yourself.

    First of all your should write some template wrapper class which has virtual clone method, returning copy of stored object. And then write some polymophic holder of that class which would be copyable

    and don't forget about checked delete http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Checked_delete

    0 讨论(0)
  • 2021-01-02 16:27

    It's a bit late, but for future viewers: There's a ready-to-use implementation in my header-only library Aurora and its SmartPtr tutorial. With Aurora, it's trivial to implement deep-copy through smart pointers. The following code works for any copyable type T:

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

    This makes it often unnecessary to implement The Big Three/Five if your classes have pointer members.

    0 讨论(0)
  • 2021-01-02 16:31

    If you accept some requirements on your types, this can be done without requiring implementing virtual clone functions for all types. The particular requirements are that the types have accessible copy constructors, which some would deem undesirable because of potential for accidental slicing. Proper use of friending may mitigate the drawbacks of that, though.

    If such is acceptable one can go about this by erasing the derived types under an interface that provides copy functionality:

    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;
    };
    

    This interface is implemented by a class templated on the most derived type, and thus knows how to make normal copies through the copy constructor.

    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;
    };
    

    This will generate virtual copy functions for us without extra boilerplate. Now we can build a smart pointer-like class on top of this (not quite a smart pointer because it does not give pointer semantics, but value semantics instead).

    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;
    };
    

    This is just a minimal interface, but it can easily be fleshed out from here to cover more usage scenarios.

    0 讨论(0)
  • 2021-01-02 16:41

    You have two solutions ( actually you have many more, but these make most sense to me :) ):

    First, you can use std::unique_ptr. This is a good solution because it forces you to have one instance per pointer. (using std::shared_ptr instead would work as well, but if you do not add the code explicitely, copy and assignment for shared_ptr will "share" - especially what you want to avoid).

    If you use std::unique_ptr, your copy constructor and assignment operator should explicitly deep-copy (using either a virtual clone method in the pointee's interface, or new operator in the call to the unique_ptr constructor).

    Second, you can roll your own. There's nothing complicated about it, and we're talking about a small (10-20 lines or so) utility class.

    Personally, if I had to use this smart pointer class in one place, I would use std::unique_ptr. Otherwise (multiple pointers, same behavior) I would roll my own, simply so I wouldn't have to repeat the deep copy for many instances (to keep with the DRY principle).

    0 讨论(0)
提交回复
热议问题