【问题标题】:Using CRTP with virtual inheritance将 CRTP 与虚拟继承一起使用
【发布时间】:2014-03-26 12:13:41
【问题描述】:

我有一个节点层次结构,其中可以出现“钻石”。

每个节点都必须是可克隆的,但我不想将克隆方法写入每个节点。所以我使用 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 中有 BUG。 有没有办法克服这个bug,并且还有协变类型是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"; }
};

我很好奇的两条线在哪里。 (第(1)和(2)行)

如果这两行都被删除,则会出现编译错误,因为存在模棱两可的 clone 方法(来自每个父级)。由于我不使用协变返回类型,因此每个父级都应该有 clone 方法,所以我使用第 (1) 行,但它不起作用。还是模棱两可。

所以我使用第 (2) 行并且它有效。

有什么好的方法可以避免写第 (2) 行吗?

HERE 是 ideone 上的完整工作示例。

【问题讨论】:

    标签: c++ crtp virtual-inheritance


    【解决方案1】:

    首先,您应该非常小心地对虚拟基内的成员使用虚拟继承(查看https://stackoverflow.com/a/1193516/1918154,“有效的 C++”,第 20 项:“避免公共接口中的数据成员”和http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.8)。你的node 得到一个指向fill 的指针,它没有被使用,但看起来你在某个地方需要它。

    当您在NodeWrap 的基类中移动继承关系(public virtual 和public)时,您的问题可以解决。

    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>
    { 
       //...
    };
    

    正在运行Example。

    InheritBases 中的纯虚方法是必需的,因为所谓的支配规则 (Dominance in virtual inheritance)。

    要解决的问题是如何在多个基数的情况下将参数传递给正确的构造函数。与Node 不同(这是一个虚拟基础),可以让NodeB 和NodeC 拥有成员变量和非平凡的构造函数。

    【讨论】:

      【解决方案2】:

      每个虚函数在每个派生类中都必须有一个唯一的最终覆盖器。这与名称查找无关(要求是函数,而不是它们的名称),因此using 无关紧要。

      使用多基类节点类模板:

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

      至于协变返回,如果方便的话,它们是完全没有必要的。您始终可以将每个虚拟功能拆分为私有虚拟和公共非虚拟。如果您想返回协变智能指针,这很方便,常规协变返回机制根本不支持。

      【讨论】:

      • 关于协变返回的更多细节:任何时候你想调用clone(),或者你已经知道你想调用的实例的最衍生的具体类型它打开,在这种情况下,您不需要虚拟方法; 或者你不知道,在这种情况下你的代码不能使用任何比Base*更具体的返回类型。
      猜你喜欢
      • 1970-01-01
      • 2021-06-16
      • 2013-12-18
      • 1970-01-01
      • 2021-09-04
      • 2016-10-26
      • 1970-01-01
      • 2013-08-24
      相关资源
      最近更新 更多