SO 5.7 InDepth Message Holder - Stiffstream/sobjectizer GitHub Wiki
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));