Inheritance in curiously recurring template pattern polymorphic copy (C++)

前端 未结 2 417
无人及你
无人及你 2020-12-19 16:03

I\'m using CRTP to add a clone method to inherited classes, for example:

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

te         


        
相关标签:
2条回答
  • 2020-12-19 16:41

    This is a rework of my answer to this question

    Your intent is to have all the derived classes in your hierarchy inherit cloneability (polymorphic copy) from their base class so that you do not also need to provide each of them with an override of clone(), but your attempted CRTP solution with class template BaseCopyable can only only confer cloneability in this way upon classes immediately derived from Base, and not upon classes derived from such derived classes.

    I do not think it is not possible to propagate cloneability right down an arbitrarily deep hierarchy by confering cloneability "just once" at the topmost concrete classes. You must explicitly confer it on each concrete class, but you can do this via their base classes and without repetitiously overriding clone(), by using a CRTP template that relays cloneability from parent class to child in the hierarchy.

    Clearly, a CRTP template that fits this bill will differ from BaseCopyable by requiring two template parameters: the parent type and the child type.

    A C++03 solution is as illustrated by the following program:

    #include <iostream>
    
    // As base of D, this makes D inherit B and makes D cloneable to
    // a polymorphic pointer to B
    template<class B, class D>
    struct cloner : virtual B
    {
        virtual B *clone() const {
            return new D(dynamic_cast<D const&>(*this));
        }
        virtual ~cloner() {}       
    };
    
    struct Base 
    {
        virtual ~Base() {
             std::cout << "I was a Base" << std::endl;
        };
        virtual Base* clone() const = 0;
    }; 
    
    struct A : cloner<Base,A> // A inherits Base
    {
        virtual ~A() {
             std::cout << "I was an A" << std::endl;
        };
    };
    
    struct B : cloner<Base,B> // B inherits Base
    {
        virtual ~B() {
             std::cout << "I was a B" << std::endl;
        };
    };
    
    struct DB : cloner<B,DB> // DB inherits B, Base
    {
        virtual ~DB() {
             std::cout << "I was a DB" << std::endl;
        };
    };
    
    int main()
    {
        Base * pBaseA = new A;
        Base * pBaseB = new B;
        Base * pBaseDB = new DB;
        Base * pBaseCloneOfA = pBaseA->clone();
        Base * pBaseCloneOfB = pBaseB->clone();
        Base *pBaseCloneOfDB = pBaseDB->clone();
        B * pBCloneOfDB = dynamic_cast<B*>(pBaseDB->clone());
        std::cout << "deleting pBaseA" << std::endl; 
        delete pBaseA;
        std::cout << "deleting pBaseB" << std::endl;
        delete pBaseB;
        std::cout << "deleting pBaseDB" << std::endl;
        delete pBaseDB;
        std::cout << "deleting pBaseCloneOfA" << std::endl;
        delete pBaseCloneOfA;
        std::cout << "deleting pBaseCloneOfB" << std::endl; 
        delete pBaseCloneOfB;
        std::cout << "deleting pBaseCloneOfDB" << std::endl;    
        delete pBaseCloneOfDB;
        std::cout << "deleting pBCloneOfDB" << std::endl;   
        delete pBCloneOfDB;
        return 0;
    }
    

    The output is:

    deleting pBaseA
    I was an A
    I was a Base
    deleting pBaseB
    I was a B
    I was a Base
    deleting pBaseDB
    I was a DB
    I was a B
    I was a Base
    deleting pBaseCloneOfA
    I was an A
    I was a Base
    deleting pBaseCloneOfB
    I was a B
    I was a Base
    deleting pBaseCloneOfDB
    I was a DB
    I was a B
    I was a Base
    deleting pBCloneOfDB
    I was a DB
    I was a B
    I was a Base
    

    Provided that all the classes involved are default constructible, B need not be a virtual base of cloner<B,D> and you can remove the virtual keyword from struct cloner : virtual B. Otherwise, B must be a virtual base so that a non-default constructor of B can be called by a constructor of D, although B is not a direct base of D.

    In C++11, where we have variadic templates, you can do without virtual inheritance altogether by furnishing cloner<B,D> with an "all-purpose" template constructor through which it can forward arbitrary constructor arguments from D to B. Here is an illustration of that:

    #include <iostream>
    
    template<class B, class D>
    struct cloner : B
    {
        B *clone() const override {
            return new D(dynamic_cast<D const&>(*this));
        }
        ~cloner() override {}
        // "All purpose constructor"
        template<typename... Args>
        explicit cloner(Args... args)
        : B(args...){}  
    };
    
    struct Base 
    {
        explicit Base(int i)
        : _i(i){}   
        virtual ~Base() {
             std::cout << "I was a Base storing " << _i << std::endl;
        };
        virtual Base* clone() const = 0;
    protected:
        int _i;
    }; 
    
    struct A : cloner<Base,A>
    {
        explicit A(int i)
        : cloner<Base,A>(i){}
        ~A() override {
             std::cout << "I was an A storing " << _i << std::endl;
        };
    };
    
    struct B : cloner<Base,B>
    {
        explicit B(int i)
        : cloner<Base,B>(i){}
        ~B() override {
             std::cout << "I was a B storing " << _i << std::endl;
        };
    };
    
    struct DB : cloner<B,DB>
    {
        explicit DB(int i)
        : cloner<B,DB>(i){}
        ~DB() override {
             std::cout << "I was a DB storing " << _i << std::endl;
        };
    };
    
    int main()
    {
        Base * pBaseA = new A(1);
        Base * pBaseB = new B(2);
        Base * pBaseDB = new DB(3);
        Base * pBaseCloneOfA = pBaseA->clone();
        Base * pBaseCloneOfB = pBaseB->clone();
        Base * pBaseCloneOfDB = pBaseDB->clone();
        B * pBCloneOfDB = dynamic_cast<B*>(pBaseDB->clone());
        std::cout << "deleting pA" << std::endl; 
        delete pBaseA;
        std::cout << "deleting pB" << std::endl;
        delete pBaseB;
        std::cout << "deleting pDB" << std::endl;
        delete pBaseDB;
        std::cout << "deleting pBaseCloneOfA" << std::endl;
        delete pBaseCloneOfA;
        std::cout << "deleting pBaseCloneOfB" << std::endl; 
        delete pBaseCloneOfB;
        std::cout << "deleting pBaseCloneOfDB" << std::endl;    
        delete pBaseCloneOfDB;
        std::cout << "deleting pBCloneOfDB" << std::endl;   
        delete pBCloneOfDB;
        return 0;
    }
    

    And the output is:

    deleting pA
    I was an A storing 1
    I was a Base storing 1
    deleting pB
    I was a B storing 2
    I was a Base storing 2
    deleting pDB
    I was a DB storing 3
    I was a B storing 3
    I was a Base storing 3
    deleting pBaseCloneOfA
    I was an A storing 1
    I was a Base storing 1
    deleting pBaseCloneOfB
    I was a B storing 2
    I was a Base storing 2
    deleting pBaseCloneOfDB
    I was a DB storing 3
    I was a B storing 3
    I was a Base storing 3
    deleting pBCloneOfDB
    I was a DB storing 3
    I was a B storing 3
    I was a Base storing 3
    
    0 讨论(0)
  • 2020-12-19 16:52

    What you can do is to propagate the base through the whole inheritance hierarchy, but I don't think this will be particularly useful as for every further derived class you now get a whole new hierarchy and all the polymorphism is going to be for naught.

    #include <iostream>
    
    class Base 
    {
    public:
         virtual ~Base() {};
         virtual Base* clone() const = 0;
    }; 
    
    template<class Derived> class BaseCopyable : Base
    { 
    public:
        virtual Base* clone() const
        {
            return new Derived(static_cast<Derived const&>(*this));
        }
    };
    
    struct Default;
    
    template<typename Self, typename Arg>
    struct SelfOrArg {
      typedef Arg type;
    };
    
    template<typename Self>
    struct SelfOrArg<Self, Default> {
      typedef Self type;
    };
    
    template<typename Derived = Default>
    class A : public BaseCopyable< typename SelfOrArg<A<Derived>, Derived>::type >
    {
    
    };
    
    class derivedA : A<derivedA> {
    
    };
    

    Although this still has the drawback of the broken return type for BaseCopyable. With a classic virtual constructor idiom, you get the ability to say something like:

    void func(Derived& d) {
      // thanks to covariant return types Derived::clone returns a Derived*
      Derived* d2 = d.clone();
      delete d2;
    }
    

    This wont be possible with your scheme, although easily possible through adjusting the return type in BaseCopyable.

    Just write a macro to get rid of the boilerplate :)

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