【问题标题】:How to dynamic cast from base to child class when the child is stored on a vector of base pointers当子类存储在基指针向量上时如何从基类动态转换为子类
【发布时间】:2017-02-26 15:49:13
【问题描述】:

我已将 Child 对象的共享指针存储在 Base 共享指针的向量 中,我需要将 Base 向量的元素动态转换为其 Child类型,这样我就可以使用儿童特定签名调用函数。

下面是一个例子。第一个代码块定义了类层次结构和我想使用的“识别”函数。第二个代码块给出了一个具体示例,说明我想如何调用特定于 TYPE 的“识别”函数,因为我可以将原始对象类型从 Base 类转换为 Child 类(例如 A ,B,C)。

有什么模式或技术可以解决这个问题吗?

#include <iostream>
#include <memory>
#include <vector>

class Base {};
class A : public Base{};
class B : public Base{};
class C : public Base{};

class CollectionOfBase
{
public:
    void add (std::shared_ptr<Base> item){m_items.push_back(item);}
    std::vector<std::shared_ptr<Base>> const& getItems() const {return m_items;}

private:
    std::vector<std::shared_ptr<Base>> m_items;
};

// I want to use these 3 functions instead of identify( std::shared_ptr<Base> const& )
void identify( std::shared_ptr<A> const& )
{
    std::cout << "A" << std::endl;
}
void identify( std::shared_ptr<B> const& )
{
    std::cout << "B" << std::endl;
}
void identify( std::shared_ptr<C> const& )
{
    std::cout << "C" << std::endl;
}

//This function works in the below for loop, but this is not what I need to use
void identify( std::shared_ptr<Base> const& )
{
    std::cout << "Base" << std::endl;
}

下面,你可以找到第二个代码块:

int main()
{
    using namespace std;

    CollectionOfBase collection;

    collection.add(make_shared<A>());
    collection.add(make_shared<A>());
    collection.add(make_shared<C>());
    collection.add(make_shared<B>());

    for (auto const& x : collection.getItems())
    {
        // THE QUESTION:
        // How to distinguish different type of items
        // to invoke "identify" with object specific signatures (e.g. A,B,C) ???
        // Can I cast somehow the object types that I push_back on my Collection ???
        // Note that this loop does not know the add order AACB that we pushed the Child pointers.

        identify(x);
    }
    /*
    The desired output of this loop should be:

    A
    A
    C
    B

    */

    return 0;
}

代码也可以在Ideone上找到。

【问题讨论】:

    标签: c++ dynamic polymorphism c++14 dispatch


    【解决方案1】:

    您可以在这里采用三种方法:OO 和动态调度、访问者和变体。哪个更好将取决于您拥有多少 types 以及您拥有多少 操作 - 以及您更有可能添加到哪一个。

    1. 实际使用OO。如果您需要每个派生对象以不同的方式执行某些操作,那么在 OO 中执行此操作的方法是添加一个虚拟成员函数:

      struct Base { virtual const char* name() = 0; };
      struct A : Base { const char* name() override { return "A"; }
      // ...
      
      for (auto const& x : collection.getItems()) {
          std::cout << x->name() << std::endl;
      }
      
    2. 使用访客模式。这介于 OO 和函数式之间——我们创建了一个知道如何与所有类型交互的基础对象:

      struct Visitor;
      struct Base { virtual void visit(Visitor& ) = 0; };
      struct A;
      struct B;
      struct C;
      
      struct Visitor {
          virtual void visit(A& ) = 0;
          virtual void visit(B& ) = 0;
          virtual void visit(C& ) = 0;
      };
      
      struct A : Base { void visit(Visitor& v) override { v.visit(*this); } };
      // ...
      
      struct IdentityVisitor : Visitor {
          void visit(A& ) { std::cout << "A" << std::endl; }
          void visit(B& ) { std::cout << "B" << std::endl; }
          void visit(C& ) { std::cout << "C" << std::endl; }
      };
      
      IdentityVisitor iv;
      for (auto const& x : collection.getItems()) {
          x->visit(iv);
      }
      
    3. 只需使用一个变体。与其存储shared_ptr&lt;Base&gt; 的集合,不如存储variant&lt;A,B,C&gt; 的集合,其中这些类型甚至不在层次结构中。它们只是三种任意类型。然后:

      for (auto const& x : collection.getItems()) {
          visit(overload(
              [](A const& ){ std::cout << "A" << std::endl; },
              [](B const& ){ std::cout << "B" << std::endl; },
              [](C const& ){ std::cout << "C" << std::endl; }
              ), x);
      }
      

    【讨论】:

    • 我猜您的第三种方法是访问者模式的“更智能”实现。
    • @csguth 只是静态访问和动态访问的区别。两者各有优缺点,不知道哪一个更好。当然,如果您有一百万种类型但只有少量操作,那么两者都不是很好。
    【解决方案2】:

    Visitor pattern 解决了这个问题。

    基本上,添加一个虚方法Base::accept(Visitor&amp; v)

    每个孩子都会覆盖这种调用v.visit(*this)的方法。

    您的访问者类应如下所示:

    class Visitor
    {
    public:
      void visit(A&) { /* this is A */ }
      void visit(B&) { /* this is B */ }
      void visit(C&) { /* this is C */ }
    }
    

    实例化您的访问者:Visitor v;

    迭代你的向量调用x-&gt;accept(v);

    http://ideone.com/2oT5S2

    【讨论】:

      【解决方案3】:

      @Barry 和@csguth 谢谢你们的回答。

      巴里的第三个选项很有趣,我想试一试。 boost::static_visitorboost::variant 的工作示例可以在 page 上找到。

      就我而言,我没有考虑 OO 和虚拟方法,因为我想避免将逻辑放入这些 A、B、C 对象中。关于访问者模式,这是我想到的唯一好的选择。不过,我希望能发现一些更灵活的解决方案,比如“LambdaVisitor”,谢谢你让我大开眼界!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-12
        • 1970-01-01
        • 2019-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多