Skip to content

SO 5.7 InDepth Message Holder

eao197 edited this page Feb 9, 2021 · 2 revisions

The message_holder_t class

The class message_holder_t is intended to be used with preallocated messages:

class some_agent final : public so_5::agent_t
{
   // A message to be sent by this agent.
   class do_something { ... };

   // A single preallocated instance of that message.
   so_5::message_holder_t<do_something> msg_;
   ...
   void on_some_cmd(mhood_t<some_command> cmd) {
      ...
      // The preallocated message should be sent to someone.
      so_5::send(cmd->target_, msg_);
      ...
   }
   ...
};

This class can also be used for storing a received message:

class demo final : public so_5::agent_t {
   // Stored message.
   so_5::message_holder_t<some_message> stored_msg_;
   ...
   void on_message(mhood_t<some_message> cmd) {
      // Store the message to redirect it later.
      stored_msg_ = cmd.make_holder();
      ...
   }
   void on_some_external_event(mhood_t<some_event>) {
      // It's time to redirect the stored message.
      so_5::send(some_target, stored_msg_);
   }
};

The class message_holder_t has several unique features:

First of all, it correctly handles the distinctions between messages inherited from so_5::message_t and messages of arbitrary user types. If a message has type T and that type is inherited from so_5::message_t an instance of message_holder_t holds a pointer to T. If T is not derived from so_5::message_t then message_holder_t will hold a pointer to the special internal wrapper so_5::user_type_message_t<T>. But getter's of message_holder_t will always return a pointer/reference to T:

struct so5_message final : public so_5::message_t { ... };
struct user_message final { ... };

so_5::message_holder_t<so5_message> one{...}; // Holds pointer to so5_message.
so_5::message_holder_t<user_message> two{...}; // Holds pointer to so_5::user_type_message_t<user_message>.

so5_message * p1 = one.get(); // Pointer to so5_message is returned.
user_message * p2 = two.get(); // Pointer to user_message is returned.

The second feature is the correct handling of message mutability. It means that message_holder_t allows to write:

so_5::message_holder_t<my_message> msg{...}; // An instance of immutable message.
so_5::message_holder_t<so_5::immutable_msg<my_message>> msg2{...}; // Another immutable message.
so_5::message_holder_t<so_5::mutable_msg<my_message>> msg3{...}; // A mutable message.

The getters from message_holder_t are depended on message mutability: for immutable messages they return const pointers/references, but for mutable messages they return non-const pointers:

so_5::message_holder_t<my_message> immutable_msg{...};
auto v = immutable_msg->some_field; // Ok.
immutable_message->some_field = v; // WON'T COMPILE!

so_5::message_holder_t<so_5::mutable_msg<my_message>> mutable_msg{...};
auto v = mutable_msg->some_field; // Ok.
mutable_message->some_field = v; // Ok.

The third feature is the ability to work as different smart pointers. The message_holder_t class is a kind of smart pointer; it holds the only pointer to the message instance, not the copy of the message.

By default, the behavior of message_holder_t depends on the message's mutability. For immutable messages, it works as std::shared_ptr. For mutable messages it works as std::unique_ptr:

so_5::message_holder_t<my_message> msg{...};
so_5::message_holder_t<my_message> msg2 = msg; // msg2 and msg point to the same message instance.

so_5::message_holder_t<so_5::mutable_msg<my_message>> msg3{...};
so_5::message_holder_t<so_5::mutable_msg<my_message>> msg4 = msg3; // WON'T COMPILE!
so_5::message_holder_t<so_5::mutable_msg<my_message>> msg5 = std::move(msg3); // Ownership moved to msg5.

However, the behavior can be specified manually as a template parameter. It allows to have std::unique_ptr-like behavior for immutable messages or std::shared_ptr-like behavior for mutable messages:

using unique_my_message = so_5::message_holder_t<
   my_message,
   so_5::message_ownership_t::unique>;

unique_my_message msg{...};
unique_my_message msg2 = msg; // WON'T COMPILE!
unique_my_message msg3 = std::move(msg); // Ownership moved to msg3.

using shared_another_messsage = so_5::message_holder_t<
   so_5::mutable_msg<another_message>,
   so_5::message_ownership_t::shared>;

shared_another_message msg4{...};
shared_another_message msg5 = msg4; // msg5 and msg4 point to the same message instance.

Another feature is the ability to construct message object inplace (with respect to inheritance from so_5::message_t and message's mutability):

class so5_message final : public so_5::message_t {
   int a_;
   const std::string b_;

   so5_message(int a, std::string b) : a_{a}, b_{std::move(b)} {}
};

so_5::message_holder_t<so5_message> msg{std::piecewise_construct, 0, "Hello");
// Or, another variant:
auto msg2 = so_5::message_holder_t<so5_message>::make(0, "Hello");

class user_message final {
  std::string str_;
  std::chrono::milliseconds time_;
};

so_5::message_holder_t<so_5::mutable_msg<user_message>> msg3{std::piecewise_construct, "Hello", 125ms);
// Or, another variant:
auto msg4 = so_5::message_holder_t<so_5::mutable_msg<user_message>>::make("Hello", 125ms);

The class message_holder_t can also be used for sending a message of a devired class as an instance of a message of the base class. For example:

class message_base : public so_5::message_t {
  ... // Some basic stuff.
};

class specific_message : public message_base {
  ... // Some additional stuff.
};

// A message handler.
void some_agent::on_incoming_message(mhood_t<message_base> cmd) {
  const message_base * base_object = cmd.get();
  ... // Work with basic stuff.

  const specific_message * derived_object =
    dynamic_cast<specific_message>(base_object);
  if(derived_object) {
    ... // Work with additional stuff.
  }
}

// Sending of a message of actual type specific_message as
// an instance of message_base.
so_5::message_holder_t<message_base> msg{std::make_unique<specific_message>(...)};
so_5::send(dest, std::move(msg));
Clone this wiki locally