Я поделился указателями ребенок объекты хранятся в Вектор базовых общих указателей и мне нужно динамически разыграть элементы Базовый вектор к его дочернему типу, такой, что я могу вызвать функцию с Дочерняя специфическая подпись.
Пример следует ниже. Первый блок кода определяет иерархию классов и функции «идентификации», которые я хотел бы использовать. Второй блок кода дает конкретный пример того, как Я хочу вызвать специфическую для ТИПА функцию «идентификации», учитывая, что я могу привести исходный тип объекта из базового класса в класс 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.
Здесь вы можете использовать три подхода: 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;
}
Используйте шаблон посетителя. Это на полпути между ОО и функционалом — мы создаем базовый объект, который знает, как взаимодействовать со всеми типами:
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);
}
Просто используйте вариант. Вместо хранения коллекции 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);
}
Шаблон посетителя решает проблему.
В основном, добавьте виртуальный метод 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);
,
@Barry и @csguth спасибо вам обоим за ответы.
Третий вариант Барри очень интересен, и я хотел бы попробовать. Рабочий пример с повышение :: static_visitor а также повышение :: вариант можно найти по этому страница.
В моем случае я не рассматривал ОО и виртуальные методы, так как хочу избежать размещения логики в этих объектах А, В, С. Что касается шаблона посетителя, это был единственный хороший вариант, который я имел в виду. Однако я надеялся найти более гибкое решение, такое как «LambdaVisitor», и спасибо, что открыли мне глаза!