Last active
December 24, 2015 10:59
-
-
Save komiga/6788395 to your computer and use it in GitHub Desktop.
More thorough example.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef EG_CMD_HPP_ | |
| #define EG_CMD_HPP_ | |
| #include <cstdint> | |
| #include <utility> | |
| #include <memory> | |
| namespace Cmd { | |
| // In my case there are many commands, so I am reducing the number of | |
| // extra declarations & definitions I have to express by using a | |
| // stub class with the declarations. This way I only have to specify | |
| // the /definitions/ per command type, not the declarations. | |
| // I also use macros to avoid having to modify the function prototypes | |
| // if the base ever changes, but I've left those out for brevity. | |
| using ID = std::uint32_t; | |
| // Command types | |
| enum class Type : unsigned { | |
| PrintSomething | |
| }; | |
| // Command stages; won't get into this, but there are multiple stages | |
| // for most commands. | |
| enum class StageType : unsigned { | |
| Statement | |
| }; | |
| class Stage; | |
| using StageUPtr = std::unique_ptr<Cmd::Stage>; | |
| // Interface for a command stage | |
| class Stage { | |
| public: | |
| // There is type_info for the entire command which contains | |
| // things such as stage instantiation (a function reference), but | |
| // I've again elided it for brevity. This also contains | |
| // serialization descriptors. | |
| struct type_info final { | |
| Cmd::StageType const type; | |
| }; | |
| private: | |
| Cmd::ID m_id; | |
| // Implementation | |
| protected: | |
| virtual Cmd::Stage::type_info const& | |
| get_stage_type_info_impl() const noexcept = 0; | |
| virtual void execute_impl(Cmd::Stage&) = 0; | |
| public: | |
| virtual ~Stage() = 0; | |
| Stage() = default; | |
| // Properties | |
| Cmd::Stage::type_info const& | |
| get_stage_type_info() const noexcept { | |
| return get_stage_type_info_impl(); | |
| } | |
| Cmd::ID get_id() const noexcept { | |
| return m_id; | |
| } | |
| void set_id(Cmd::ID const id) noexcept { | |
| m_id = id; | |
| } | |
| // Operations | |
| // Normally handle preconditions here | |
| void execute(Cmd::Stage& initiator) { | |
| execute_impl(initiator); | |
| } | |
| }; | |
| inline Stage::~Stage() = default; | |
| // Stub for a command implementation | |
| template< | |
| Cmd::Type command_type, | |
| Cmd::StageType stage_type, | |
| class Data | |
| > | |
| class StageImpl final : public Stage { | |
| private: | |
| Data m_data; | |
| Cmd::Stage::type_info const& | |
| get_stage_type_info_impl() const noexcept override; | |
| void execute_impl(Cmd::Stage&) override; | |
| public: | |
| ~StageImpl() override = default; | |
| StageImpl() = delete; | |
| StageImpl(Data&& data) | |
| : m_data(std::move(data)) | |
| {} | |
| }; | |
| } // namespace Cmd | |
| #endif // EG_CMD_HPP_ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "Cmd.hpp" | |
| #include "CmdPrintSomething.hpp" | |
| #include <iostream> | |
| namespace Cmd { | |
| // This is normally handled through a macro. | |
| // Because all of the stage classes are only local to the | |
| // implementation file for a command, I simply name them after the | |
| // stage type. | |
| struct Statement final { | |
| int x; | |
| using impl = ::Cmd::StageImpl< | |
| Cmd::Type::PrintSomething, | |
| Cmd::StageType::Statement, | |
| Statement | |
| >; | |
| }; | |
| // I actually expose type_info to another system to implement a | |
| // constant-time type lookup table, so I ensure the name is unique to | |
| // the command and stage. | |
| static Cmd::Stage::type_info const | |
| s_type_info_PrintSomething_Statement{ | |
| Cmd::StageType::Statement | |
| }; | |
| // C++ requires the definitions of a class to be defined in the | |
| // class' namespace. | |
| // I don't want the actual definitions for a stage to be in that | |
| // namespace because it pollutes the interface namespace (Cmd) and | |
| // requires group definitions to be separated from the command | |
| // definition. | |
| // My argument with namespace deduction here is that a compiler | |
| // should be able to understand (and in fact they do) that the | |
| // definition pertains to a very specific class (outside of the | |
| // namespace of definition) and should be able to compile it without | |
| // fuss. | |
| // E.g., if the enclosing namespace were Print (as it should be) | |
| // instead of Cmd, this alias still points specifically to | |
| // Cmd::StageImpl, not any other StageImpl. | |
| // My inquiry is thus: why and where is this not permitted in the | |
| // standard? The full namespace of the class is known. Both Clang and | |
| // GCC understand the situation perfectly, too, judging by their error | |
| // messages. | |
| // The prototypes here are also handled through macros. | |
| template<> | |
| Cmd::Stage::type_info const& | |
| Statement::impl::get_stage_type_info_impl() const noexcept { | |
| return s_type_info_PrintSomething_Statement; | |
| } | |
| // It is very nice to have direct access to m_data here. | |
| // With Daryle Walker's suggestion, it is not as direct. | |
| template<> | |
| void Statement::impl::execute_impl(Cmd::Stage&) { | |
| std::cout | |
| << "group Print, command Something, stage Statement\n" | |
| << "x = " << m_data.x << '\n' | |
| ; | |
| } | |
| // Here are the actual public definitions for the Print command group. | |
| // Luckily my structure has command groups under Cmd. If this were not | |
| // the case, I would have to break out of Cmd, open Print, and refer | |
| // to the stage classes through Cmd. | |
| // Furthermore, if I wanted the stage classes to be outside of Cmd, | |
| // I'd have to break out twice: | |
| // namespace Print { declarations } | |
| // namespace Cmd { implementation } | |
| // namespace Print { public interface } | |
| namespace Print { | |
| Cmd::StageUPtr make_something(int const x) { | |
| return Cmd::StageUPtr{new Statement::impl({ | |
| x, | |
| })}; | |
| } | |
| } // namespace Print | |
| } // namespace Cmd |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef EG_CMD_PRINT_SOMETHING_HPP_ | |
| #define EG_CMD_PRINT_SOMETHING_HPP_ | |
| #include "Cmd.hpp" | |
| namespace Cmd { | |
| namespace Print { | |
| // This contains all of the command initiators for a group | |
| // (and potentially other things). | |
| // Used to create an initiator stage for the PrintSomething command. | |
| Cmd::StageUPtr make_something(int const x); | |
| } // namespace Print | |
| } // namespace Cmd | |
| #endif // EG_CMD_PRINT_SOMETHING_HPP_ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "Cmd.hpp" | |
| #include "CmdPrintSomething.hpp" | |
| int main() { | |
| Cmd::StageUPtr s{Cmd::Print::make_something(42)}; | |
| // Command initiator is always the first stage | |
| s->execute(*s.get()); | |
| return 0; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
GitHub screwed up the order. Thanks, GitHub.
Compiling with: