当子项存储在基指针向量上时,如何从基类动态转换为子类

时间:2017-02-26 15:49:13

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

我已经分享了存储在基础共享指针向量中的 Child 对象的指针,我需要动态地将 Base向量的元素强制转换为其子对象输入,这样我就可以使用子特定签名 来调用函数。

以下是一个例子。第一个代码块定义了类层次结构,并且"标识"我想使用的功能。第二个代码块给出了一个具体的例子,说明我想如何调用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上找到。

3 个答案:

答案 0 :(得分:2)

Visitor pattern解决了这个问题。

基本上,添加虚拟方法Base::accept(Visitor& 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->accept(v);

http://ideone.com/2oT5S2

答案 1 :(得分:2)

您可以采取以下三种方法:OO和动态调度,访问者和变体。无论哪个更好,取决于您拥有多少类型以及您拥有多少操作 - 以及您更有可能添加的操作

  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<Base>的集合,而是存储variant<A,B,C>的集合,其中这些类型甚至不在层次结构中。他们只是三种任意类型。然后:

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

答案 2 :(得分:1)

@Barry和@csguth非常感谢你们的答案。

巴里的第三个选择非常有趣,我想尝试一下。可以在 page 上找到 boost :: static_visitor boost :: variant 的工作示例。

就我而言,我没有考虑OO和虚方法,因为我想避免将逻辑放入这些A,B,C对象中。关于访客模式,这是我心中唯一的好选择。但是,我希望能找到一些更灵活的解决方案,例如&#34; LambdaVisitor&#34;谢谢你开阔我的眼睛!