如何使用 CRTP 自注册类实例?

How to self register class instances using the CRTP?

本文关键字:实例 注册 何使用 CRTP      更新时间:2023-10-16

>我有一个与特定CommandId关联的lambda函数的注册表,其中注册的函数应该创建一个命令执行器类的具体实例并为其提供一些数据:

CommandId.h

#include <cstdint>
enum class CommandId : uint16_t {
Command1 ,
Command2 ,
};

注册表.h

#include <map>
#include <functional>
#include <string>
#include "CommandId.h"
class Registry
{
public:
static std::map<CommandId,std::function<void (std::string)>>& 
GetCommands() {
static std::map < CommandId, std::function<void(std::string)>> 
theFunctionRegistry;
return theFunctionRegistry;
}
};

现在我的想法是提供具体的命令实现作为来自 CRTP 模板库的派生类,它提供自身的static成员只是为了注册关联的 lambda 函数(因为调用派生类的工作流只是样板代码):

CommandBase.h

#include "CommandId.h"
#include "Registry.h"
// The general command execution interface
struct ICommand {
virtual void Execute(const std::string& data) = 0;
virtual ~ICommand() {}
};
template<typename Derived, CommandId CmdId>
class CommandBase : public ICommand
{
public:
virtual ~CommandBase() {}
void Register() {}
protected:
// Dummy function to avoid abstract instantiations, should probably throw
void Execute(const std::string& data) override {
}
// Protected constuctor meant for concrete command classes
CommandBase(int& derivedRef) : derivedRef_(&derivedRef) {}
// The static member that should perform the registation automagically
static CommandBase<Derived, CmdId> registryAdder_;
// This constructor is meant to be accessed from the above static member
// instantiation only, and just register the lambda function 
CommandBase() : derivedRef_(nullptr) {
// Register a lambda function that ususe a concrete Derived instance
Registry::GetCommands()[CmdId] = [](const std::string& data) {
Derived derivedInstance;
CommandBase<Derived, CmdId>* der = static_cast<CommandBase<Derived, CmdId>*>(&derivedInstance);
// Manipulate the derived instances data and execute
*(der->derivedRef_) = 42;
der->Execute(data);
};
}
// Provides access to the derived class instances data members and allows manipulation 
int* derivedRef_;
};
template<typename Derived, CommandId CmdId>
CommandBase<Derived, CmdId> CommandBase<Derived, CmdId>::registryAdder_;

我以为访问 base 的static registryAdder_成员会强制编译器提供实例化,但这并没有发生。

这是Command1Command2的试用版

<h3>Command1.h</h3>
#include "CommandBase.h"
class Command1 : public CommandBase<Command1, CommandId::Command1>
{
public:
typedef CommandBase<Command1, CommandId::Command1> BaseClass;
friend class BaseClass;
public:
Command1(CommandId) {
BaseClass::registryAdder_.Register();
}
virtual ~Command1() override {}
private:
Command1() : BaseClass(member_)
{
BaseClass::registryAdder_.Register();
}
void Execute(const std::string& data) override {
}
private:
int member_;
};

命令2.h

#include "CommandBase.h"
class Command2 : public CommandBase<Command2, CommandId::Command2>
{
public:
typedef CommandBase<Command2, CommandId::Command2> BaseClass;
friend class BaseClass;
public:
Command12CommandId) {
BaseClass::registryAdder_.Register();
}
virtual ~Command1() override {}
private:
Command2() : BaseClass(member_)
{
BaseClass::registryAdder_.Register();
}
void Execute(const std::string& data) override {
}
private:
int member_;
};

我希望这条线

BaseClass::registryAdder_.Register();

在构造函数中将强制实例化BaseClass::registryAdder_static基类成员。但它显然没有,编译器只是把它剥离掉:

#include <iostream>
#include "Command1.h"
#include "Command2.h"
#include "Registry.h"
int main()
{
std::cout << "There are " << Registry::GetCommands().size() << " registered commands." << std::endl;
return 0;
}

输出:

There are 0 registered commands.

现在我的问题是:

如何强制编译器从CommandBase模板类实例化这些static基类成员?


当然,我做过一些研究,但这些问答并没有真正让我满意:

  • 如何强制初始化静态成员?
  • 强制模板静态成员实例化
  • 派生类中的显式模板静态成员实例化
  • 。也许更多

我决定根据 Yakk 的修复重新打开问题和自我回答。似乎是一个更常见的问题(例如,请参阅此处)

Yakk的例子解决了它:

#include <cstdint>
enum class CommandId : uint16_t {
Command1 ,
Command2 ,
};

#include <map>
#include <functional>
#include <string>
//#include "CommandId.h"
class Registry
{
public:
static std::map<CommandId,std::function<void (std::string)>>& 
GetCommands() {
static std::map < CommandId, std::function<void(std::string)>> 
theFunctionRegistry;
return theFunctionRegistry;
}
};
// #include "CommandId.h"
// #include "Registry.h"
// The general command execution interface
struct ICommand {
virtual void Execute(const std::string& data) = 0;
virtual ~ICommand() {}
};
template<typename Derived, CommandId CmdId>
class CommandBase : public ICommand
{
public:
virtual ~CommandBase() {}
void Register() {}
protected:
// Dummy function to avoid abstract instantiations, should probably throw
void Execute(const std::string& data) override {
}
// Protected constuctor meant for concrete command classes
CommandBase(int& derivedRef) : derivedRef_(&derivedRef) {}
// The static member that should perform the registation automagically
static CommandBase<Derived, CmdId> registryAdder_;
// This constructor is meant to be accessed from the above static member
// instantiation only, and just register the lambda function 
CommandBase() : derivedRef_(nullptr) {
// Register a lambda function that ususe a concrete Derived instance
Registry::GetCommands()[CmdId] = [](const std::string& data) {
Derived derivedInstance;
CommandBase<Derived, CmdId>* der = static_cast<CommandBase<Derived, CmdId>*>(&derivedInstance);
// Manipulate the derived instances data and execute
*(der->derivedRef_) = 42;
der->Execute(data);
};
}
// Provides access to the derived class instances data members and allows manipulation 
int* derivedRef_;
};
template<typename Derived, CommandId CmdId>
CommandBase<Derived, CmdId> CommandBase<Derived, CmdId>::registryAdder_;
// #include "CommandBase.h"
class Command1 : public CommandBase<Command1, CommandId::Command1>
{
public:
typedef CommandBase<Command1, CommandId::Command1> BaseClass;
friend class CommandBase<Command1, CommandId::Command1>;
public:
Command1(CommandId) {
BaseClass::registryAdder_.Register();
}
virtual ~Command1() override {}
private:
Command1() : BaseClass(member_)
{
BaseClass::registryAdder_.Register();
}
void Execute(const std::string& data) override {
}
private:
int member_;
};
// #include "CommandBase.h"
class Command2 : public CommandBase<Command2, CommandId::Command2>
{
public:
typedef CommandBase<Command2, CommandId::Command2> BaseClass;
friend class CommandBase<Command2, CommandId::Command2>;
public:
Command2(CommandId) {
BaseClass::registryAdder_.Register();
}
virtual ~Command2() override {}
private:
Command2() : BaseClass(member_)
{
BaseClass::registryAdder_.Register();
}
void Execute(const std::string& data) override {
}
private:
int member_;
};
#include <iostream>
//#include "Command1.h"
//#include "Command2.h"
//#include "Registry.h"
int main()
{
std::cout << "There are " << Registry::GetCommands().size() << " registered commands." << std::endl;
return 0;
}

需要在派生类中使用这些static实例。