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

Я поделился указателями ребенок объекты хранятся в Вектор базовых общих указателей и мне нужно динамически разыграть элементы Базовый вектор к его дочернему типу, такой, что я могу вызвать функцию с Дочерняя специфическая подпись.

Пример следует ниже. Первый блок кода определяет иерархию классов и функции «идентификации», которые я хотел бы использовать. Второй блок кода дает конкретный пример того, как Я хочу вызвать специфическую для ТИПА функцию «идентификации», учитывая, что я могу привести исходный тип объекта из базового класса в класс 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.

0

Решение

Здесь вы можете использовать три подхода: OO и динамическая рассылка, посетитель и вариант. Какой из них лучше, будет зависеть от того, сколько типы у вас есть и сколько операции у вас есть — а также какой из которых вы, скорее всего, добавите.

  1. На самом деле используйте 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. Используйте шаблон посетителя. Это на полпути между ОО и функционалом — мы создаем базовый объект, который знает, как взаимодействовать со всеми типами:

    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

Другие решения

Шаблон посетителя решает проблему.

В основном, добавьте виртуальный метод 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

2

@Barry и @csguth спасибо вам обоим за ответы.

Третий вариант Барри очень интересен, и я хотел бы попробовать. Рабочий пример с повышение :: static_visitor а также повышение :: вариант можно найти по этому страница.

В моем случае я не рассматривал ОО и виртуальные методы, так как хочу избежать размещения логики в этих объектах А, В, С. Что касается шаблона посетителя, это был единственный хороший вариант, который я имел в виду. Однако я надеялся найти более гибкое решение, такое как «LambdaVisitor», и спасибо, что открыли мне глаза!

1
По вопросам рекламы [email protected]