Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
286 views
in Technique[技术] by (71.8m points)

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

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

class Base 
{
     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));
    }
};

class A : public BaseCopyable<A>;
class B : public BaseCopyable<B>;
etc...

But if I have a class that inherits from B, for example:

class differentB : public B;

Then clone() doesn't return an object of type differentB, it returns a B. Besides writing a new clone() method in differentB, is there some way to fix this?

Thanks for reading!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...