у меня есть Message
структура, которую я использую с шиной сообщений, и я хотел бы отправить данные с сообщениями. Проблема в том, что данные будут различаться по типу; может быть, для одного сообщения я просто хочу отправить одно целое, но для другого я хочу отправить несколько целых чисел, строку, может быть, даже указатель на объект, например. я мог сделать что-то вроде этого:
struct Message {
std::map<int, int> intPayload;
std::map<int, std::string> strPayload;
short id;
};
Но это не только уродливо и нечисто, и, вероятно, тратит впустую пространство, но это не учитывает, если я хочу передать относительно экзотический тип данных, например, указатель на экземпляр класса. Что я должен использовать для этого?
Есть много способов сделать это. Вот пример с C ++ 17 std::variant
:
std::vector<std::variant<int, std::string>> vec1;
vec1.emplace_back(1);
vec1.emplace_back("hello"s);
doSomethingWithInt( std::get<int>(vec1[0]) );
doSomethingWithString( std::get<std::string>(vec1[1]) );
vec1
это список элементов, которые либо int
или же std::string
,
Вы также можете использовать статический посетитель:
std::vector<std::variant<int, std::string>> vec2;
// ...
for(auto&& variant : vec1) {
variant.visit([](auto value){
using t = decltype(value);
if constexpr (std::is_same_v<t, int>) {
std::cout << "value is a int!" << std::endl;
} else if constexpr (std::is_same_v<t, std::string>) {
std::cout << "value is a string!" << std::endl;
}
});
}
Простой пример использования наследования и полиморфизма:
struct MessageBase
{
// The function to send *this* message to the receiver
virtual void send(ReceiverClass*) = 0;
};
struct MessageInt : MessageBase
{
int payload;
void send(ReceiverClass* receiver)
{
// Code to send this message type to the receiver...
}
};
struct MessageString : MessageBase
{
std::string payload;
void send(ReceiverClass* receiver)
{
// Code to send this message type to the receiver...
}
};
// ...
// Vector to store the messages
std::vector<MessageBase*> messages;
// Add a couple of messages
messages.push_back(new MessageInt{123});
messages.push_back(new MessageString{"Foobar"});
// Send the message to some receiver
for (auto const* message : messages)
message->send(some_reciver_object);
любой хорошая книга должен быть в состоянии дать вам больше информации.
Вы можете основывать свое решение на схеме посетителей.
Как минимальный рабочий пример:
struct Listener;
struct Message {
virtual void accept(Listener &) = 0;
};
struct SimpleMessage: Message {
void accept(Listener &) override;
int i;
};
struct ComplexMessage: Message {
void accept(Listener &) override;
int i;
char c;
double d;
};
struct Listener {
void visit(SimpleMessage &) {}
void visit(ComplexMessage &) {}
void listen(Message &m) { m.accept(*this); }
};
void SimpleMessage::accept(Listener &l) { l.visit(*this); }
void ComplexMessage::accept(Listener &l) { l.visit(*this); }
struct Bus {
Bus(Listener *l): l{l} {}
void publish(Message &m) { l->listen(m); }
private:
Listener *l;
};
int main() {
Listener l;
Bus b{&l};
SimpleMessage sm;
ComplexMessage cm;
b.publish(sm);
b.publish(cm);
}
Отложить в сторону тот факт, что реализация для Bus
тривиально, обратите внимание, что visit
функции-члены в Listener
может быть виртуальным.
Таким образом, все ваши слушатели могут быть получены из этого класса и переопределить нужные методы.
Bus
примет набор Listener
s, независимо от того, что является фактическим производным типом, и общим Message
, С другой стороны, сообщение будет продвигаться к правильному производному типу и передавать ссылку на данного слушателя.
Техника, лежащая в основе шаблона посетителя, также известна как двойная диспетчеризация, если вы хотите изучить это дальше.