Copy constructor of a class containing a smart pointer
Asked Answered
U

1

0

In the following example we have a class Class that contains a Bridge object that takes care of all the memory handling for us (rule of three).

class Base {
public:
    Base() {};
    virtual Base* clone() const = 0;
    virtual ~Base() {};
};

class Derived : public Base {
public:
    Derived() {};
    virtual Base* clone() const {
        return new Derived(*this);
    }
    virtual ~Derived() {}
};

class Bridge {
public:
    Bridge(const Bridge& bridge_) {
        base = bridge_.base->clone();
    }
    Bridge(const Base& base_) {
        base = base_.clone();
    }
    ~Bridge() { delete base; }
    
    Bridge& operator=(const Bridge& assignFrom) {
        if(this != &assignFrom) {
            delete base;
            base = assignFrom.base->clone();
        }
        return *this;
    }
private:
    Base *base;
};

class Class {
public:
    Class(const Bridge& bridge_) : bridge(bridge_) {};
private:
    Bridge bridge;
};

int main()
{
    Derived derived;
    Class c(derived);
    Class c1(c);  
}

Now, I have just learned about smart pointers and was trying to recreate the above example using unique_ptr. To my understanding, we basically don't need to implement the rule of 3 ourselves as the smart pointer contains it already. To test this, I made the following example:

class BaseSMRT {
public:
    BaseSMRT() {};
    virtual std::unique_ptr<BaseSMRT> clone() const = 0;
    virtual ~BaseSMRT() {};
};

class DerivedSMRT : public BaseSMRT {
public:
    DerivedSMRT() {};
    virtual std::unique_ptr<BaseSMRT> clone() const {
        return std::make_unique<DerivedSMRT>(*this);
    }
    virtual ~DerivedSMRT() {}
};

class ClassSMRT {
public:
    ClassSMRT(const BaseSMRT& base) {
        ptr = base.clone();
    };
private:
    std::unique_ptr<BaseSMRT> ptr;
};

int main()
{
    DerivedSMRT derivedSMRT;
    ClassSMRT cSMRT(derivedSMRT);
    ClassSMRT cSMRT2(cSMRT); // error: Call to implicitly-deleted copy constructor of 'ClassSMRT'
}

As you can see in the above example, initialising cSMRT2 with cSMRT through the copy constructor doesn't work and gives me the above error.

I don't get this: Why is it that I can call Class's default copy constructor like this, Class c1(c);, but not call ClassSMRT's default copy constructor, ClassSMRT cSMRT2(cSMRT);?

This suggests that the rule of three isn't already implemented for us when we are using unique_ptr.

Usurp answered 25/9, 2020 at 7:56 Comment(8)
Please be more specific than "doesn't work".Kamikamikaze
std::unique_ptr is non-copyable. It doesn't have a copy constructor that makes a copy of the object that is held by unique_ptr.Rammer
@Rammer Thanks - so you are telling me that the rule of 3 is not implemented for us when using std::unique_ptr? I would still have to implement it myself? In that case, I can't see what the usage is of std::unique_ptr is in this particular caseUsurp
For unique_ptr it is a rule of 5, but copy operations are deleted. If you want to copy an object, you should do it yourself. It would be semantically wrong for a smart pointer to make such a copy - after all, it is just a pointer.Rammer
I find that it helps to think of unique_ptr not as a pointer, but as a unique object – it can't be copied, it can only be moved.Kamikamikaze
smart pointers prevent your program from going horribly wrong unexpectedly when you violate the rule of 3/5, you still might need to implement them in order to get your desired behaviourFourdrinier
@AlanBirtles Thanks! If I was to make my example work then, is all I have to do simply to use std::move in the clone?Usurp
@TylerD No, you need to follow the rule of 3/5 in ClassSMRT. You need to define what copying an object that has a non-copyable member means in your particular case.Kamikamikaze
I
2

unique_ptr is designed to stop accidental copies. You want to implicitly clone you polymorphic type when copied. unique_ptr doesn't (directly) fit your uses.

I would suggest a rename of Bridge

template </*Cloneable*/ typename T>
class clone_ptr {
public:
    clone_ptr(const T& base_) 
      : base(base_.clone()) {}
    clone_ptr(const clone_ptr& other)
      : base(other.base->clone()) {}
    clone_ptr(clone_ptr&& other) = default;
    
    clone_ptr& operator=(clone_ptr other) {
        base = std::move(other.base);
        return *this;
    }
private:
    std::unique_ptr<T> base;
};
Iridosmine answered 25/9, 2020 at 8:17 Comment(3)
Shouldn't operator= take clone_ptr by const-ref?Rammer
@Rammer taking by value covers move and copy, if I implemented it how I intended. I blame a lack of coffee atmIridosmine
Taking by value with a move is a nice solution.Rammer

© 2022 - 2024 — McMap. All rights reserved.