Наследование в любопытно повторяющихся шаблонах полиморфной копии (C ++) - PullRequest
4 голосов
/ 24 февраля 2012

Я использую CRTP для добавления метода клонирования в унаследованные классы, например:

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

Но если у меня есть класс, который наследуется от B, например:

class differentB : public B;

Тогда clone () не возвращает объект типа DifferentB, он возвращает B. Помимо написания нового метода clone () в DifferentB, есть ли способ это исправить?

Спасибо за чтение!

Ответы [ 2 ]

3 голосов
/ 17 мая 2013

Это переделка моего ответа на этот вопрос

Ваша цель состоит в том, чтобы все производные классы были в вашей иерархии наследовать клонируемость (полиморфную копию) от их базового класса, так что вам также не нужно предоставлять каждому из них переопределение clone(), но ваша попытка CRTP-решения с шаблоном класса BaseCopyable может только таким способом придать клонируемость классы, производные от Base, а не от производных классов из таких производных классов.

Я не думаю, что невозможно распространять клонирование прямо вниз сколь угодно глубокой иерархии путем присвоения клонируемости «только один раз» в самые верхние конкретные классы. Вы должны явно предоставить это каждому конкретный класс, но вы можете сделать это через свои базовые классы и без повторного переопределения clone(), используя CRTP шаблон, который передает клонируемость от родительского класса к ребенку в иерархия.

Очевидно, что шаблон CRTP, который соответствует этому счету, будет отличаться от BaseCopyable требуя два параметров шаблона: родительский тип и дочерний тип.

Решение C ++ 03 показано на следующей программе:

#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;
}

Вывод:

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

При условии, что все участвующие классы являются конструируемыми по умолчанию, B необязательно быть виртуальной базой cloner<B,D>, и вы можете удалить virtual ключевое слово от struct cloner : virtual B. В противном случае B должно быть виртуальной базой. так что конструктор не по умолчанию B может быть вызван конструктором D, хотя B не является прямым основанием D.

В C ++ 11, где у нас есть переменные шаблоны, вы можете обойтись без виртуальных наследование в целом путем снабжения cloner<B,D> «универсальным» конструктор шаблона, с помощью которого он может переслать произвольный конструктор аргументы от D до B. Вот иллюстрация этого:

#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;
}

И вывод:

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 голосов
/ 24 февраля 2012

Что вы можете сделать, это распространить базу по всему наследованию иерархия, но я не думаю, что это будет особенно полезно для каждый последующий производный класс вы теперь получаете совершенно новую иерархию и все полиморфизм будет напрасным.

#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> {

};

Хотя это все еще имеет недостаток неработающего типа возврата для BaseCopyable. С классической virtual constructor идиомой вы получаете способность сказать что-то вроде:

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

Это не возможно с вашей схемой, хотя легко возможно путем настройки типа возврата в BaseCopyable.

Просто напишите макрос, чтобы избавиться от шаблона:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...