Использование CRTP с виртуальным наследованием

У меня есть иерархия узлов, где может возникнуть «алмаз».

Каждый узел должен быть клонируемым, но я не хочу писать метод клонирования для каждого узла. Поэтому я использую CRTP.

class Node
{
public:
    Node(){}
    Node(Fill*) { }

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

    virtual void id() { std::cout << "Node\n"; }
};

//====================================================================

template <typename Base, typename Derived>
class NodeWrap : public Base
{
public:

    NodeWrap() { } 
    NodeWrap(Fill * arg1) : Base(arg1) { }

    virtual Node *clone() const
    {
        return new Derived(static_cast<Derived const &>(*this));
    }
};

работает следующим образом:

class NodeA : public NodeWrap<Node, NodeA>
{
public:
    typedef NodeWrap<Node, NodeA> BaseClass;

    NodeA() { }
    NodeA(Fill * f) : BaseClass(f) { }

    virtual void id() { std::cout << "NodeA\n"; }

}; 

Первый вопрос:

Известно ОШИБКА в VS, когда "ковариация используется с виртуальным наследованием". Есть ли способ преодолеть ошибку, и все еще иметь ковариантные типы - это метод clone?

Я изменил тип возвращаемого значения на Node вместо Base. Я могу жить с этим, но я хотел бы иметь Base в качестве возвращаемого типа

Второй вопрос: возникла проблема, когда в игру вступает множественное наследование. Я создал новую оболочку, которая наследует virtually

template <typename Base, typename Derived>
class NodeWrapVirtual : public virtual Base
{
public:

    NodeWrapVirtual() { }
    NodeWrapVirtual(Fill * arg1) : Base(arg1) { }

    virtual Node *clone() const
    {
        return new Derived(static_cast<Derived const &>(*this));
    }
};

и теперь строим ромбовидную структуру:

class NodeB : public NodeWrapVirtual<Node, NodeB>
{
public:
typedef NodeWrapVirtual<Node, NodeB> BaseClass;

NodeB() { }
NodeB(Fill * f) : BaseClass(f) { }

virtual void id() { std::cout << "NodeB\n"; }
};

//====================================================================

class NodeC : public NodeWrapVirtual<Node, NodeC>
{
public:
    typedef NodeWrapVirtual<Node, NodeC> BaseClass;

    using BaseClass::clone;

    NodeC() { }
    NodeC(Fill * f) : BaseClass(f) { }

    virtual void id() { std::cout << "NodeC\n"; }
};

и проблемный алмазный узел:

class NodeD : public NodeWrap<NodeB, NodeD>,
              public NodeWrap<NodeC, NodeD>
{
public:

    typedef NodeWrap<NodeB, NodeD>  BaseClassB;
    typedef NodeWrap<NodeC, NodeD>  BaseClassC;

    NodeD() { }
    NodeD(Fill * f) : BaseClassB(f), BaseClassC(f) { }

    using BaseClassB::clone;  // (1)
    virtual NodeD *clone() const { return new NodeD(*this); }       // (2)

    virtual void id() { std::cout << "NodeD\n"; }
};

где 2 строки, которые меня интересуют. (строка (1) и (2))

Если обе строки удалены, возникает незаметная ошибка компиляции, потому что есть неоднозначный метод clone (от каждого родителя). Поскольку я не использую ковариантные возвращаемые типы, должен работать метод clone для каждого родителя, поэтому я использую строку (1), но она не работает. Еще неоднозначно.

Поэтому я использую строку (2), и это работает.

Есть ли хороший способ избежать написания строки (2)?

ЗДЕСЬ — полный рабочий пример ideone.


person relaxxx    schedule 26.03.2014    source источник


Ответы (2)


Во-первых, вы должны быть очень осторожны при использовании виртуального наследования с членами внутри виртуальной базы (см. /1918154, "Эффективный C++", пункт 20: "Избегайте элементов данных в общедоступных интерфейсах" и http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.8). Ваш node получает указатель на fill, который не используется, но похоже, что он вам где-то нужен.

Ваша проблема может быть решена, если вы переместите отношение наследования (public virtual и public) в базовый класс для вашего NodeWrap.

template <typename Base>
class  InheritVirtual
    : public virtual Base
{};

template <typename... Bases>
class InheritBases
    : public Bases...
{
    virtual Node* clone() const = 0;
    virtual void id() const = 0;
};

class NodeB : public NodeWrap<InheritVirtual<Node>, NodeB>
{ 
   //...
};


class NodeC : public NodeWrap<InheritVirtual<Node>, NodeB>
{ 
   //...
};

class NodeD : public NodeWrap<InheritBases<NodeB,NodeC>, NodeD>
{ 
   //...
};

Выполнение примера.

Чисто виртуальные методы в InheritBases необходимы, потому что так называемое правило доминирования (Доминирование в виртуальном наследовании) .

Проблема, которую необходимо решить, — это способ передать параметры правильному конструктору в случае нескольких баз. В отличие от Node (который является виртуальной базой), можно позволить NodeB и NodeC иметь переменные-члены и нетривиальные конструкторы.

person Jan Herrmann    schedule 01.04.2014

Каждая виртуальная функция должна иметь уникальный окончательный переопределяющий элемент в каждом производном классе. Это не имеет ничего общего с поиском имени (требование относится к функциям, а не к их именам), поэтому using не имеет значения.

Используйте шаблон класса узлов с несколькими базовыми классами:

 template <class Derived, class Base1, class Base2>
 class node2 : //  etc
 // or use a variadic template if you have more than two bases

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

person n. 1.8e9-where's-my-share m.    schedule 31.03.2014
comment
Немного подробнее о ковариантных возвратах: каждый раз, когда вы хотите вызвать clone(), либо вы уже знаете наиболее производный конкретный тип экземпляра, для которого вы хотите его вызвать, и в этом случае вы не знаете нужен виртуальный метод; или вы этого не знаете, и в этом случае ваш код все равно не может использовать какой-либо тип возвращаемого значения, более конкретный, чем Base*. - person j_random_hacker; 31.03.2014
comment
Спасибо за ваш ответ - person relaxxx; 06.04.2014