8

From the boost library documentation I read this:

Conceptually, smart pointers are seen as owning the object pointed to, and thus responsible for deletion of the object when it is no longer needed.

I have a very simple problem: I want to use RAII for pointer attributes of a class that is Copyable and Assignable.

The copy and assignment operations should be deep: every object should have its own copy of the actual data. Also, RTTI needs to be available for the attributes (their type may also be determined at runtime).

Should I be searching for an implementation of a Copyable smart pointer (the data are small, so I don't need Copy on Write pointers), or do I delegate the copy operation to the copy constructors of my objects as shown in this answer?

Which smart pointer do I choose for simple RAII of a class that is copyable and assignable? (I'm thinking that the unique_ptr with delegated copy/assignment operations to the class copy constructor and assignment operator would make a proper choice, but I am not sure)

Here's a pseudocode for the problem using raw pointers, it's just a problem description, not a running C++ code:

// 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;
}
Community
  • 1
  • 1
tmaric
  • 5,347
  • 4
  • 42
  • 75

5 Answers5

6

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.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Definitely one up for the detailed answer, thank you! However, this seems much more complex than wrapping up a pointer, and defining copy and assignment for it. It might be my lack of experience... but this seems as a rather complex approach to the problem... – tmaric Dec 17 '12 at 12:36
  • How would you make the copies in your "wrapping up a pointer, and defining copy and assignment for it" approach? Adding a `clone` function to the `ModelOperation` interface and implementing it manually in every derived class? Note that all the code in this code is to be written once and reused, not once per derived class. – R. Martinho Fernandes Dec 17 '12 at 12:45
  • Also note that your example is not exception safe: what happens if `secondOperation_ = new DifferentOperation();` throws? Not that the destructor of `MyModel` is not called if the constructor does not complete, so the `MoreSpecialOperation` created in the previous line would just leak. Using a smart pointer-like class would make it automatically safe. And all this without any boilerplate: your example becomes `class MyModel { polymorphic_value firstOperation; polymorphic_value secondOperation; /* no copy ctors, nor assignment ops, nor destructors to write */ };` – R. Martinho Fernandes Dec 17 '12 at 12:47
  • Thanks @R.MartinhoFernandes; I like this approach. From my point of view though, the blocker isn't so much the constraint on the types (all copy-constructible, etc), but more that the place where you do the ownership needs to know the definition of each most-derived type that it might store. I like the tidy way this avoids implementing clone() for each class though in those circumstances, and how it handles having several most-derived types (I think?). – Nicholas Wilson Dec 17 '12 at 12:55
  • I must be missing something here. I've written (quickly) an idea with wrapping a pointer up here: http://pastebin.com/qS27A52v and it seems to be running, I ran a valgrind check and there are no memory leaks, and the simple copy operation of the wrapped pointer delegates copy constructor to the template argument. What is wrong with this approach? – tmaric Dec 17 '12 at 13:24
  • 1
    @tomislav-maric what you are missing is the fact that you cannot use the copy constructor unless it is the one of the most derived class. In that code, you use two `wrapPtr`. That means that in this line `t_ = new T (rhs());`, the type `T` will be `ModelOperation` and this will call the `ModelOperation` constructor, not the `SpecialModelOperation` or `DifferentModelOperation` constructors. It simply loses all the information that is not in the base. – R. Martinho Fernandes Dec 17 '12 at 13:27
  • 1
    More explicitly: If you put a `SpecialModelOperation` in that `wrapPtr` and then copy it, the copy will have an instance of `ModelOperation`, not of `SpecialModelOperation`. – R. Martinho Fernandes Dec 17 '12 at 13:30
  • @R.MartinhoFernandes Thank you! :) I knew I was missing something. – tmaric Dec 17 '12 at 13:35
5

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.

TheOperator
  • 5,936
  • 29
  • 42
1

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

kassak
  • 3,974
  • 1
  • 25
  • 36
1

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.

Nicholas Wilson
  • 9,435
  • 1
  • 41
  • 80
  • I'm thinking about dropping smart pointers completely. There seems to be a conflict between the RAII which is ownership based when it comes to smart pointers, and the fact that there is no copyable smart pointer available in std:: or boost:: without the transfer of ownership or the use of references. – tmaric Dec 17 '12 at 11:17
  • 1
    @tomislav You have to have transfer of ownership or use of references unless you have a special interface (like Clonable): given an object by pointer, ordinary invocation of eg a copy-constructor can't duplicate the object. How would the compiler know which object's constructor to call--the most derived? That needs a virtual lookup, and in C++, you have to write your own clone() to get that. – Nicholas Wilson Dec 17 '12 at 11:23
  • 1
    If you can put up with some requirements on your types, you can do this without a clone function and simply work with the regular copy constructor. The solution involves automatic generation of the clone function externally via templates. (I'll see if I can write up an answer about that as soon as I get some free time; sadly my current project compiles really fast) – R. Martinho Fernandes Dec 17 '12 at 11:25
  • @NicholasWilson It seems to be the minimal workload to acomplish the task if I wrap a pointer. No smart pointer, my own small class, that does the job of the copy how I need it, and still packages the pointer so that RAII is used. – tmaric Dec 17 '12 at 12:33
0

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).

utnapistim
  • 26,809
  • 3
  • 46
  • 82