2012-02-23 7 views
3

Я использую CRTP добавить метод клонирования унаследованных классов, например:Наследования в удивительно повторяющемся узоре шаблона полиморфной копии (C++)

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; 

Затем клон() не возвращает объект типа differentB, он возвращает B. Кроме написания нового клона() метод в differentB, есть какой-то способ исправить это?

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

+2

Вы могли бы просто требовать, что только наиболее производного класса наследуют от 'BaseCopyable'. –

+3

Не является ли целью функции-члена клона, что вы можете вернуть производный тип в переопределение в производном? Это называется «ковариантными типами возврата» и идеально подходит для этой функции. – pmr

ответ

3

Это является переделка моего ответа на this question

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

Я не думаю, что невозможно размножать клонирование вплоть до произвольной глубинной иерархии , предлагая клонирование «всего один раз» на самых лучших конкретных классах. Вы должны явно наделять его на каждый конкретного класса, но вы можете сделать это с помощью своих базовых классов и без repetitiously перекрывая 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 не нужно be виртуальный база cloner<B,D> и вы можете удалить ключевое слово virtual из struct cloner : virtual B. В противном случае, B должна быть виртуальной базой так, что конструктор B не по умолчанию может быть вызвана с помощью конструктора D, хотя B не является прямой базой D.

В C++ 11, где мы имеем VARIADIC шаблонов, вы можете обойтись без виртуального наследования в целом путем предоставления 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

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

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

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