StringMessage* str_message = ...; send(my_message); ... void handle_message(const Message* message) { assert(message); const StringMessage* str_message = dynamic_cast<const StringMessage*>(message); assert(str_message); std::cout << str_message->message ... }
void handle_message(const StringMessage& message) { ... }
Message
. class Message{ public: Message(unsigned code) : code(code) {} virtual ~Message() {} const unsigned code; }; enum Code { STRING = 1 }; class StringMessage : public Message { public: StringMessage(const std::string& msg) : Message(STRING), message(msg) {} const std::string message; };
class Messenger { ... template <class T, class MessageDerived> void subscribe(int code, T* object, void (T::* method)(const MessageDerived&)) { // , - const std::type_index& arg_type = typeid(const MessageDerived); // , (const Message&) void (T::* sign)(const Message&) = (void (T::*)(const MessageDerived&)) method; // subscribers_.push_back(Subscriber(code, object, NewDelegate(object, sign), arg_type)); } }
template <class Base, class Derived> bool is_sliced(const Derived* der) { return (void*) der != (const Base*) der; }
template <class Base, class Derived> struct is_sliced2 : public std::integral_constant<bool, ((void*)((Base*)((Derived*) 1))) != (void*)1> {}; ... static_assert(!is_sliced2<Message, Arg>::value, "Message object should not be sliced");
class Messenger { ... template <class LikeMessage> void send(const LikeMessage& msg) { assert((!is_sliced<Message, LikeMessage>(&msg))); send_impl(msg); } private: void send_impl(const Message& msg) { const std::type_info& arg = typeid(msg); // for (SubscribersCI i = subscribers_.begin(); i != subscribers_.end(); ++i) { if (i->code == msg.code) { // if (arg != i->arg_type) // , throw std::logic_error("Bad message cast"); i->method->call(msg); // - } } } }
MessageDerived
indeed inherited from Message
. In C ++ 11, there is std::is_base_of
in the <type_traits>
std::is_base_of
. In C ++ 03, the compile time check will have to be written by hand. class Printer { public: void print(const StringMessage& msg) { std::cout << "Printer received: " << msg.message << std::endl; } }; int main() { Messenger messenger; Printer print; messenger.subscribe(STRING, &print, &Printer::print); messenger.send(StringMessage("Hello, messages!")); return 0; }
messenger.subscribe(STRING, [](const StringMessage& msg) {...});
std::function
, but for this you need to know the type of lambda without losing the type of the input argument. And then convert the lambda into something universal like std::function<void (const Message&)>
. But you can not just take and learn the type of C ++ lambda. template <typename Function> struct function_traits : public function_traits<decltype(&Function::operator())> {}; template <typename ClassType, typename ReturnType, typename... Args> struct function_traits<ReturnType(ClassType::*)(Args...) const> { typedef ReturnType (*pointer)(Args...); typedef std::function<ReturnType(Args...)> function; };
operator()
, which is used for the call. decltype(&Function::operator())
expands this to the type of the member function corresponding to the lambda. The arguments are passed to the partially specialized template, where the corresponding synonyms for the type of the function pointer and the std::function
for the std::function
pointer are set. template <typename Function> class Messenger { ... void subscribe(int code, Function func) { // function_traits typedef typename function_traits<Function>::function FType; // std::function argument_type ( ) typedef typename FType::argument_type Arg; // typeid auto& arg_type = typeid(Arg); // , Message // Arg . , . typedef std::remove_reference<Arg>::type ArgNoRef; // static_assert(std::is_base_of<Message, ArgNoRef>::value, "Argument type not derived from base Message"); // auto ptr = to_function_pointer(func); // , auto pass = (void(*) (const Message&)) ptr; subscribers_.emplace_back(std::move(Subscriber(code, pass, arg_type))); } }
template <typename Function> typename function_traits<Function>::pointer to_function_pointer(Function& lambda) { return static_cast<typename function_traits<Function>::pointer>(lambda); }
std::function<void (const Message&)> msg_func = ...; std::function<void (const StringMessage&)> str_func = msg_func; //
StringMessage
is a Message
. But not vice versa. int main() { Messenger messenger; messenger.subscribe(STRING, [](const StringMessage& msg) { std::cout << "Received: " << msg.message << std::endl; }); messenger.send(StringMessage("Hello, messages!")); return 0; }
Message
. And the second, which can only accept a message with the string StringMessage
. We will send one set message Msg: 13955ms Str: 1176ms Ratio: 12.0
typeid
type of the argument when sending to one message and checking for type matching. The figure is depressing, we will remember it, but still not the most important. Because most likely in the program there will be a bottleneck not in the process of sending messages, but in their processing. And in the most extreme case, you can remove the check for the type in the release mode, aligning the performance.Source: https://habr.com/ru/post/211476/
All Articles