项目中涉及到特征拼接的部分代码, 考虑到封装接口+动态绑定的实现方案, 故我们需要的是一个自动注册的模板工厂。
具体用到了: 工厂模式、生成器模式、单例模式、模板模式…
首先我们看看模板化的实现:CommonFactory.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#pragma once #include <string> #include <unordered_map> #include <memory> #include <mutex> template <typename T> class CommonBuilder_Interface { public: virtual std::shared_ptr<T> build() const = 0; }; template <typename T> class CommonFactory { private: using InstPtr = std::shared_ptr<CommonFactory<T>>; using BuilderPtr = std::shared_ptr<CommonBuilder_Interface<T>>; public: struct Register { Register(const std::string &name, const BuilderPtr &builder) noexcept { GetInstance()->AddBuilder(name, builder); } }; public: // 自行实现单例 - Singleton继承, 编译太难了... static InstPtr &GetInstance() noexcept { if (m_instance_ptr == nullptr) { std::lock_guard<std::mutex> lg(m_mutex); if (m_instance_ptr == nullptr) { m_instance_ptr = std::make_shared<CommonFactory<T>>(); } } return m_instance_ptr; } public: bool AddBuilder(const std::string &name, const BuilderPtr &builder) noexcept { std::lock_guard<std::mutex> lg(m_mutex); // 注册的时候加个锁 if (m_mapBuilderPtr.find(name) == m_mapBuilderPtr.end()) { m_mapBuilderPtr[name] = builder; return true; } return false; } std::shared_ptr<T> Create(const std::string &name) const noexcept { auto it_builder = m_mapBuilderPtr.find(name); if (it_builder != m_mapBuilderPtr.end()) { return it_builder->second->build(); } return nullptr; } private: static InstPtr m_instance_ptr; static std::mutex m_mutex; std::unordered_map<std::string, BuilderPtr> m_mapBuilderPtr; }; template <typename T> std::shared_ptr<CommonFactory<T>> CommonFactory<T>::m_instance_ptr = nullptr; template <typename T> std::mutex CommonFactory<T>::m_mutex; |
然后接下来我们定义简单的消息类型, 使用并理解CommonFactory。
创建Message文件夹,定义消息接口,它有纯虚函数可以获取具体的消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include "CommonFactory.h" namespace Message { class MessageInterface { public: virtual std::string GetMessage() = 0; }; // 消息工厂 using MessageFactory = CommonFactory<MessageInterface>; using MessageFactoryPtr = std::shared_ptr<MessageFactory>; inline MessageFactoryPtr &GetMessageFactory() { return MessageFactory::GetInstance(); } // 消息生成器 template <class T> class MessageBuilder final : public CommonBuilder_Interface<MessageInterface> { public: virtual std::shared_ptr<MessageInterface> build() const override { return std::make_shared<T>(); } }; } // 注册消息, 可通过类静态成员变量在main开始前的构造完成注册 #define RegisterMessage(ClassName) \ inline static const Message::MessageFactory::Register \ _Message_Factory_Register_##ClassName##_ = { \ #ClassName, \ std::make_shared<Message::MessageBuilder<ClassName>>()}; |
我们定义了消息接口、消息工厂、消息生成器以及一个用于注册消息的宏。
接下来在Message文件夹内创建两个具体的消息, 分别叫HelloMessage 和 HiMessage, 完成后文件路径如下:
P.s> 这里CommonFactory放哪里都可以, 我在实际项目中放的地方也不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ tree -L 2 . ├── CMakeLists.txt ├── Message │ ├── CMakeLists.txt │ ├── HelloMessage.cpp │ ├── HelloMessage.h │ ├── HiMessage.cpp │ ├── HiMessage.h │ ├── CommonFactory.h │ └── MessageInterface.h └── Test ├── CMakeLists.txt └── main.cpp |
HelloMessage.h 内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#pragma once #include "MessageInterface.h" namespace Message { class HelloMessage : public MessageInterface { RegisterMessage(HelloMessage); public: virtual std::string GetMessage() override; }; } |
HelloMessage.cpp
1 2 3 4 5 6 7 8 |
#include "HelloMessage.h" using namespace Message; std::string HelloMessage::GetMessage() { return "Hello"; } |
HiMessage.h
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#pragma once #include "MessageInterface.h" namespace Message { class HiMessage : public MessageInterface { RegisterMessage(HiMessage); public: virtual std::string GetMessage() override; }; } |
HiMessage.cpp
1 2 3 4 5 6 7 8 |
#include "HiMessage.h" using namespace Message; std::string HiMessage::GetMessage() { return "Hi"; } |
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include "Message/MessageInterface.h" int main() { auto factory = Message::MessageFactory::GetInstance(); auto msg1 = factory->Create("HelloMessage"); if (msg1 != nullptr) { std::cout << "msg1 = " << msg1->GetMessage() << std::endl; } else { std::cout << "Hello not found" << std::endl; } auto msg2 = factory->Create("HiMessage"); if (msg2 != nullptr) { std::cout << "msg2 = " << msg2->GetMessage() << std::endl; } else { std::cout << "Hi not found" << std::endl; } auto msg3 = factory->Create("None"); if (msg3 != nullptr) { std::cout << "msg3 = " << msg3->GetMessage() << std::endl; } else { std::cout << "None not found" << std::endl; } return 0; } |
CMakeLists.txt 就不展示了, 是CMake编译用的。
然后main文件在Test文件夹, 很明显会编译Message和 Test两个文件夹,然后让Test去链接Message。(这里有坑, 后文继续解决)
但是当我们编译结束之后, 运行会发现结果如下:
1 2 3 |
Hello not found Hi not found None not found |
这里的原因是: 链接静态库时,链接器会检查静态库的变量和函数是否被使用,没有使用的不会链接。
所以我们要想办法让Test去链接Message静态库, 解决方案在stackoverflow上面已经有了。
https://stackoverflow.com/questions/14116420/how-to-force-gcc-to-link-an-unused-static-library
1 2 3 4 5 6 7 8 9 10 11 12 |
Use --whole-archive linker option. Libraries that come after it in the command line will not have unreferenced symbols discarded. You can resume normal linking behaviour by adding --no-whole-archive after these libraries. In your example, the command will be: g++ -o program main.o -Wl,--whole-archive /path/to/libmylib.a In general, it will be: g++ -o program main.o \ -Wl,--whole-archive -lmylib \ -Wl,--no-whole-archive -llib1 -llib2 |
User should anyway add -Wl,-no-whole-archive at the end. As man ld says: “Second, don’t forget to use -Wl,-no-whole-archive after your list of archives, because gcc will add its own list of archives to your link and you may not want this flag to affect those as well.” – Sasha
旧的CMakeLists.txt :
1 2 3 4 5 6 7 |
aux_source_directory(. ALL_FILES) ADD_EXECUTABLE(Test ${ALL_FILES}) TARGET_LINK_LIBRARIES( Test Message ) |
修改Test的CMakeLists.txt文件如下:
1 2 3 4 5 6 7 8 9 |
aux_source_directory(. ALL_FILES) ADD_EXECUTABLE(Test ${ALL_FILES}) TARGET_LINK_LIBRARIES( Test -Wl,--whole-archive Message -Wl,--no-whole-archive ) |
重新编译, 运行输出结果如下:
1 2 3 |
msg1 = Hello msg2 = Hi None not found |
完成!
P.s> 我们还可以在定义一个Name的工厂,通过配置组合Hello/Hi, Name1/Name2得到他们的组合。
参考文档:
https://www.cnblogs.com/qicosmos/p/5090159.html
基于它的方案, 改进后, main函数只需要include接口即可,在工程上编译效率大大提高。