Trying to resolve error C2248 related to abstract base class using implementation of copy/move ctors/assignment operators and dtor (Rule of Five) and a few questions come up:
1) Why does the rule of 5, primarily relating to the dtor, apply when the unique_ptr data members are handled automatically? The dtor implementation should be left empty correct, since the unique_ptrs are automatically destroyed once their owners go out of scope?
2) Suppose another class had a member of type std::unique_ptr of a vector of the same type. In order for this class to be copyable, it must have a copy ctor and copy assignment operator that clone the unique_ptr data member? I have seen this solution, but is seems like the original poster just switched over to shared_ptr for the sake of removing the error alone with little consideration of ownership management. Is this the correct strategy?
3) Consider the same case as question 2 above relating to vector of unique_ptr. Should dtor include a call to clear() the vector?
4) The assignment operators for the Derived1 are not correct. But the base class is supposed to have copy and move assignment operators, since it has copy/move ctors (rule of 4/5). These can’t actually be used outside of the class since it is abstract and thus no instances will be assigned. But how do I utilize this code from the derived classes? Each derived class needs to be able to move/copy the base data members and it’s own data members. I’m not sure what to do.
#include <algorithm>
#include <memory>
#include <vector>
#include <iostream>
class Base{
public:
Base() : m_subBases(){};
/* copy ctor */
Base(const Base& other) : m_subBases(){
*this = other;
};
/* move ctor */
Base(Base&& other) : m_subBases(){
*this =std::move( other);
};
/* Move assignment operator*/
Base& operator=(Base&& other){
m_subBases = std::move(other.m_subBases);
return *this;
};
/* Copy assignment operator */
Base& operator=(const Base& other){
for(int i = 0; i < other.m_subBases.size(); i++)
m_subBases.push_back(other.m_subBases[i]->clone());
return *this;
};
/* virtual dtor */
virtual ~Base(){
m_subBases.clear();
};
/* Used for creating clones of unique_ptrs */
virtual std::unique_ptr <Base> clone() const= 0;
/* Do something */
virtual void execute(float f) = 0;
//Omitted data member access methods
protected:
std::vector < std::unique_ptr <Base> > m_subBases;
};
class Derived1 : public Base{
public:
Derived1() : Base(){};
/* copy ctor */
Derived1(const Derived1& other) : Base(other){
*this = other;
};
/* move ctor */
Derived1(Derived1&& other) : Base(std::move(other)){
*this = std::move(other);
};
/* Move assignment operator*/
Derived1& operator=(Derived1&& other){
//This is redundant when called in the move ctor because
// of the call to Base(std::move(other))
m_subBases = std::move(other.m_subBases);
m_string = other.m_string;
return *this;
};
/* Copy assignment operator */
Derived1& operator=( const Derived1& other){
//This is redundant when called in the copy ctor because
// of the call to Base(other)
for(int i = 0; i < other.m_subBases.size(); i++)
m_subBases.push_back(other.m_subBases[i]->clone());
m_string = other.m_string;
return *this;
};
/* virtual dtor */
virtual ~Derived1(){};
/* Used for creating clones of unique_ptrs */
virtual std::unique_ptr <Base> clone() const{
return std::unique_ptr <Base> (new Derived1(*this));
};
virtual void execute(float f){
std::cout << "Derived1 " << f << std::endl;
};
protected:
std::string m_string;
};
I’d like to offer an alternative approach. Not the Scary Rule of Five, but the Pleasant Rule of Zero, as @Tony The Lion has already suggested. A full implementation of my proposal has been coded by several people, and there’s a fine version in @R. Martinho Fernandes’s library, but I’ll present a simplified version.
First, let’s recap:
The Rule of Zero: Don’t write a copy- or move-constructor, a copy- or move-assignment operator, or a destructor. Instead, compose your class of components which handle a single responsibility and encapsulate the desired behaviour for the individual resource in question.
There’s an obvious caveat: When you design the single-responsibility class, you must of course obey:
The Rule of Five: If you write any one of copy- or move-constructor, copy- or move-assignment operator, or destructor, you must implement all five. (But the “five” functions needed by this rule are actually: Destructor, Copy-Const, Move-Const, Assignment and Swap.)
Let’s do it. First, your consumer:
Note that both
BaseandDerivedobey the Rule of Zero!All we need to do is implement
value_ptr. If the pointee is non-polymorphic, the following will do:If you would like smart pointer that handles polymorphic base class pointers, I suggest you demand that your base class provide a virtual
clone()function, and that you implement aclone_ptr<T>, whose copy constructor would be like this: