From faab095b4c6a36254a219168acf83cb01a9accd1 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 9 Jun 2026 22:06:28 +0200 Subject: [PATCH 1/2] refactor(backmp11)!: remove front-end constructor forwarding --- doc/modules/ROOT/pages/backmp11-back-end.adoc | 37 +-- doc/modules/ROOT/pages/version-history.adoc | 9 +- .../backmp11/detail/state_machine_base.hpp | 19 ++ include/boost/msm/backmp11/state_machine.hpp | 79 ++--- test/Backmp11Constructor.cpp | 285 ++---------------- test/Backmp11Context.cpp | 59 +--- test/Backmp11CopyMove.cpp | 36 ++- test/Backmp11Visitor.cpp | 4 +- test/Constructor.cpp | 3 + test/TestConstructorMovableOnlyTypes.cpp | 3 + 10 files changed, 146 insertions(+), 388 deletions(-) diff --git a/doc/modules/ROOT/pages/backmp11-back-end.adoc b/doc/modules/ROOT/pages/backmp11-back-end.adoc index 8c597825..c56f3f4e 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end.adoc @@ -88,30 +88,33 @@ struct MyStateMachineConfig : state_machine_config The state machine configuration is designed to be shared in hierarchical state machines. -=== Context -The setting `context` sets up a context member in the state machine for dependency injection. +=== Root state machine -If `using context = MyContext;` is defined in the config, the following API becomes available to access it in the state machine: +The setting `root_sm` defines the type of the root state machine in hierarchical state machines. The root sm is the uppermost state machine. + +If `using root_sm = MyRootSm;` is defined in the config, the following API becomes available to access it from any (sub-)machine: ```cpp -MyContext& state_machine::get_context(); +MyRootSm& state_machine::get_root_sm(); ``` -If a context is configured, a reference to a context instance must be passed as the first argument to the state_machine constructor. +It is highly recommended to always configure the `root_sm` in hierarchical state machines, even if access to it is not required. +This reduces compilation time and memory footprint, because it enables the back-end to instantiate the full set of methods and members only for the root and it can omit them for submachines. -=== Root state machine -The setting `root_sm` defines the type of the root state machine of hierarchical state machines. The root sm is the uppermost state machine. +=== Context -If `using root_sm = MyRootSm;` is defined in the config, the following API becomes available to access it from any (sub-)machine: +The setting `context` sets up a context member in the state machine for dependency injection. + +If `using context = MyContext;` is defined in the config, the following API becomes available to access it in the state machine: ```cpp -MyRootSm& state_machine::get_root_sm(); +MyContext& state_machine::get_context(); ``` -It is highly recommended to always configure the `root_sm` in hierarchical state machines, even if access to it is not required. -This reduces compilation time and memory footprint, because it enables the back-end to instantiate the full set of methods and members only for the root and it can omit them for submachines. +If a context is configured, a reference to an instance of it must be passed as the first argument to the state_machine constructor. +In hierarchical state machines the root state machine has to be set as well. === `Fsm` parameter of actions and guards @@ -294,11 +297,6 @@ You can also defer events in transitions by using the `front::Defer` action. Whi The state machine also contains a xref:reference:boost/msm/backmp11/state_machine/defer_event.adoc[`void state_machine::defer_event(const Event&)`] method, which defers the processing to the next time `process_event(...)` or `process_event_pool(...)` is called. -== Handling exceptions - -The back-end is exception-neutral; exceptions thrown during processing are propagated to the caller. -It provides a _basic exception guarantee_: The processing aborts mid-execution, but the state machine remains in a valid state. If the processing step involves a state switch, the active state switches to the target state as soon as the source state's exit action has completed. If you need to know exact details about the aborted state machine activities during event processing, see the xref:#_run_to_completion[run-to-completion algorithm]. - == Check for active states @@ -363,6 +361,11 @@ my_state_machine.visit ``` +== Exceptions + +The back-end is exception-neutral; exceptions thrown during processing are propagated to the caller. +It provides a _basic exception guarantee_: The processing aborts mid-execution, but the state machine remains in a valid state. If the processing step involves a state switch, the active state switches to the target state as soon as the source state's exit action has completed. If you need to know exact details about the aborted state machine activities during event processing, see the xref:#_run_to_completion[run-to-completion algorithm]. + == Reflection and serialization @@ -593,7 +596,7 @@ Futhermore, the back-end forwards Kleene events without converting them to `std: There were some caveats with one constructor that was used for different use cases: On the one hand, some arguments were immediately forwarded to the front-end's constructor, on the other hand, the stream operator was used to identify other arguments in the constructor as states, to copy them into the state machine. Besides the syntax of the latter being rather unusual, when doing both at once, the syntax becomes too difficult to understand; even more so if states within hierarchical submachines are initialized in this fashion. -To keep the API of the constructor simpler and less ambiguous, it only supports forwarding arguments to the front-end. +To keep the API of the constructor simpler and less ambiguous, it only supports arguments related to the `state_machine` configuration. The state machine's front-end must be default-constructible. The `set_states(...)` API has also been removed. If setting a state is required, this can still be done (in a more verbose, but also more direct and explicit way) by getting a reference to the desired state via `get_state()` and then assigning a new state object to it. diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index 3628bdcd..9c1ad646 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -4,16 +4,17 @@ == Boost 1.92 -* **Breaking changes (`backmp11`)**: -** Make enqueuing events explicit (https://github.com/boostorg/msm/issues/178[#178]) -** The APIs `process_queued_events()` and `process_single_queued_event()` are removed, use `process_event_pool(...)` instead -** The default active state switch policy is set to "after source exit" in compliance to the UML specification. The other options are not UML-compliant and will be removed in version 1.93 (https://github.com/boostorg/msm/issues/222[#222]) * New features (`backmp11`): ** Forward Kleene events to actions and guards without conversion (https://github.com/boostorg/msm/issues/229[#229]) ** Ensure basic exception guarantee and propagate exceptions to the caller (https://github.com/boostorg/msm/issues/221[#221]) ** Provide a reflection API and serialization support for Boost.Serialization, Boost.JSON and nlohmann/json (https://github.com/boostorg/msm/issues/197[#197]) * Bug fixes (`backmp11`): ** State machine processes events although it is not running (https://github.com/boostorg/msm/issues/198[#198]) +* **Breaking changes (`backmp11`)**: +** Events in `process_event(...)` are not enqueued automatically anymore to reduce memory footprint, use `enqueue_event(...)` in actions (https://github.com/boostorg/msm/issues/178[#178]) +** The back-end does not forward constructor arguments to the front-end anymore, it must be default-constructible. To use custom constructors, define them in the back-end (https://github.com/boostorg/msm/issues/232[#232]) +** The APIs `process_queued_events()` and `process_single_queued_event()` are removed, use `process_event_pool(...)` instead +** The default active state switch policy is set to "after source exit" in compliance to the UML specification. The other options are not UML-compliant and will be removed in version 1.93 (https://github.com/boostorg/msm/issues/222[#222]) * Documentation: ** API reference is now generated from source with MrDocs diff --git a/include/boost/msm/backmp11/detail/state_machine_base.hpp b/include/boost/msm/backmp11/detail/state_machine_base.hpp index fd06b777..5205687c 100644 --- a/include/boost/msm/backmp11/detail/state_machine_base.hpp +++ b/include/boost/msm/backmp11/detail/state_machine_base.hpp @@ -38,6 +38,10 @@ class non_propagating { } + non_propagating(const non_propagating&) + { + } + non_propagating& operator=(const non_propagating&) { return *this; @@ -107,6 +111,16 @@ class context_member protected: static constexpr bool has_context_member = true; + context_member(Context& context) noexcept : m_context(&context) {} + + context_member(const context_member&) noexcept = default; + + context_member& operator=(const context_member&) noexcept = default; + + context_member(context_member&&) noexcept = default; + + context_member& operator=(context_member&&) noexcept = default; + private: template friend class backmp11::state_machine; @@ -215,6 +229,11 @@ class state_machine_base } protected: + using context_member = + detail::context_member; + + using context_member::context_member; + machine_state get_machine_state() const { return m_machine_state; diff --git a/include/boost/msm/backmp11/state_machine.hpp b/include/boost/msm/backmp11/state_machine.hpp index 828e0ff0..25be25e1 100644 --- a/include/boost/msm/backmp11/state_machine.hpp +++ b/include/boost/msm/backmp11/state_machine.hpp @@ -80,14 +80,14 @@ class state_machine "FrontEnd must be a composite state"); public: + /// Type of the front-end (same as FrontEnd). + using front_end_t = FrontEnd; /// Type of the configuration (same as Config, see @ref state_machine_config). using config_t = typename state_machine_base::config_t; - /// Type of the root machine (see @ref state_machine_config::root_sm). - using root_sm_t = typename state_machine_base::root_sm_t; /// Type of the context (see @ref state_machine_config::context). using context_t = typename state_machine_base::context_t; - /// Type of the front-end (same as FrontEnd). - using front_end_t = FrontEnd; + /// Type of the root machine (see @ref state_machine_config::root_sm). + using root_sm_t = typename state_machine_base::root_sm_t; /// Type of the derived machine (corresponds to Derived). using derived_t = mp11::mp_if_c, state_machine, Derived>; @@ -225,13 +225,8 @@ class state_machine using history_impl = detail::history_impl; - struct front_end_init_tag {}; - - template - state_machine(front_end_init_tag, Args&&... args) - : front_end_t(std::forward(args)...) + void init() { - // Same logic as default constructor static_assert( std::is_base_of_v, "Derived must inherit from state_machine"); @@ -242,70 +237,48 @@ class state_machine using visitor_t = detail::init_state_visitor; visitor_t visitor{self()}; detail::visit_if(self(), visitor); + visitor_t::template predicate>(self(), visitor); } } public: - state_machine() : state_machine(front_end_init_tag{}) - { - } - /** - * @brief Constructs and forwards further constructor arguments to the - * front-end. + * @brief Constructs and forwards constructor arguments to the back-end. * - * @param arg Constructor argument for the front-end. - * @param args Constructor arguments for the front-end. + * Requires constructor arguments as configured: + * - Context& (if context = Context is set) */ - template , - std::decay_t>::value>, - typename... Args> - state_machine(Arg&& arg, Args&&... args) - : state_machine(front_end_init_tag{}, std::forward(arg), - std::forward(args)...) + template + state_machine(Args&&... args) + : state_machine_base(std::forward(args)...) { + init(); } - /** - * @brief Constructs with a context and forwards further constructor - * arguments to the front-end. - * - * @param context The context. - * @param args Constructor arguments for the front-end. - */ - template , - typename... Args> - state_machine(context_t& context, Args&&... args) - : state_machine(front_end_init_tag{}, std::forward(args)...) + // Copy constructor. + state_machine(state_machine const& rhs) + : state_machine_base(rhs), m_states(rhs.m_states), + m_active_state_ids(rhs.m_active_state_ids) { - if constexpr (!std::is_same_v) - { - static_assert( - std::is_constructible_v, - "Derived must inherit the base class constructors"); - } - this->m_context = &context; + init(); } - // Copy constructor. - state_machine(state_machine const& rhs) : state_machine() + state_machine(state_machine& rhs) + : state_machine_base(rhs), m_states(rhs.m_states), + m_active_state_ids(rhs.m_active_state_ids) { - *this = rhs; + init(); } // Copy assignment operator. state_machine& operator=(state_machine const& rhs) = default; // Move constructor. - state_machine(state_machine&& rhs) : state_machine() + state_machine(state_machine&& rhs) + : state_machine_base(std::move(rhs)), m_states(std::move(rhs.m_states)), + m_active_state_ids(rhs.m_active_state_ids) { - *this = std::move(rhs); + init(); } // Move assignment operator. diff --git a/test/Backmp11Constructor.cpp b/test/Backmp11Constructor.cpp index b2ca555d..ab199a35 100644 --- a/test/Backmp11Constructor.cpp +++ b/test/Backmp11Constructor.cpp @@ -12,7 +12,7 @@ // back-end #include "BackCommon.hpp" //front-end -#include +#include "FrontCommon.hpp" #ifndef BOOST_MSM_NONSTANDALONE_TEST #define BOOST_TEST_MODULE test_constructor #endif @@ -20,7 +20,6 @@ #include namespace msm = boost::msm; -namespace mpl = boost::mpl; using namespace msm::front; using namespace msm::backmp11; @@ -28,272 +27,36 @@ using namespace msm::backmp11; namespace { - struct SomeExternalContext - { - SomeExternalContext(int b):bla(b){} - int bla; - }; - // events - struct play {}; - struct end_pause {}; - struct stop {}; - struct pause {}; - struct open_close {}; - struct NextSong {}; - struct PreviousSong {}; - // A "complicated" event type that carries some data. - enum DiskTypeEnum - { - DISK_CD=0, - DISK_DVD=1 - }; - struct cd_detected - { - cd_detected(std::string name, DiskTypeEnum diskType) - : name(name), - disc_type(diskType) - {} +struct MyState : test::StateBase {}; - std::string name; - DiskTypeEnum disc_type; - }; - - template