Как самостоятельно зарегистрировать экземпляры класса с помощью CRTP?

У меня есть реестр для лямбда-функций, связанных с конкретным CommandId, где зарегистрированная функция должна создать конкретный экземпляр класса исполнителя команд и предоставить ему некоторые данные:

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 только для регистрации связанной лямбда-функции (поскольку рабочий процесс для вызова производных классов будет просто шаблонным кодом) :

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_;

Я думал, что доступ к элементу static registryAdder_ базы заставит компилятор предоставить экземпляр, но этого не происходит.

Вот суд для Command1 и Command2

<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.

Теперь мой вопрос:

Как заставить компилятор создавать экземпляры этих static членов базового класса из CommandBase класса шаблона?


Я, конечно, провел некоторое исследование, но эти вопросы и ответы меня не очень удовлетворили:


person πάντα ῥεῖ    schedule 09.06.2017    source источник
comment
Я мог бы поделиться полным проектом VS2017 через GitHub gist или другие каналы по запросу.   -  person πάντα ῥεῖ    schedule 09.06.2017
comment
Почему вы возвращаете копию пустой статической карты всякий раз, когда кто-то звонит GetCommands? Чем это... полезно? Я предполагаю, что это приведет к тому, что вы всегда будете говорить, что есть 0 команд каждый раз, когда вы спрашиваете, что объясняет ваш код.   -  person Yakk - Adam Nevraumont    schedule 09.06.2017
comment
@Yakk THX за указание, что это была просто опечатка, когда я воссоздавал MCVE из головы.   -  person πάντα ῥεῖ    schedule 09.06.2017
comment
Публикуйте фактические минимальные воспроизводимые примеры, а не случайный код, который выглядит как один. Пожалуйста, создайте ссылку на онлайн-компилятор, который скомпилирует ваш код и сгенерирует ваши симптомы, поскольку ваш код, в основном похожий на MCVE, явно не является доказательством того, что вы действительно запускали и проверяли его.   -  person Yakk - Adam Nevraumont    schedule 09.06.2017
comment
@Yakk Весь этот код компилируется. Позвольте мне сшить это вместе в coliru или где-то еще, просто дайте мне минуту. (Я знаю, что я здесь делаю!)   -  person πάντα ῥεῖ    schedule 09.06.2017
comment
@πάνταῥεῖ Вы кажетесь такими уверенными, но строка Command12CommandId) { и виртуальный деструктор ~Command1() в классе Command2 начинают отличаться.   -  person François Andrieux    schedule 09.06.2017
comment
Живой пример с исправленными опечатками и зарегистрированными двумя командами. Вы должны проверить, подключен ли ваш код, будучи уверенным, что код, который вы на самом деле не тестировали, делает то, что вы думаете, просто не работает. Я постоянно сталкиваюсь с этой проблемой, и меня спасает только то, что матра действительно подключена. Я понятия не имею, чем код, сгенерировавший вашу проблему, отличается от кода, который я разместил в живом примере, и я никак не могу понять это, если вы не опубликуете фактический MCVE.   -  person Yakk - Adam Nevraumont    schedule 09.06.2017
comment
Согласно моему отладчику, для ваших команд существуют символы registryAdder_. Мне кажется, что все, что вам нужно сделать, это закончить свою функцию CommandBase::Register. Пусто.   -  person François Andrieux    schedule 09.06.2017
comment
@Yakk Ну, позвольте мне снова вернуться к своей работе :-P   -  person πάντα ῥεῖ    schedule 09.06.2017
comment
@Yakk Почему бы не опубликовать свои исправления в качестве ответа? По крайней мере, мой код скомпилирован с использованием VS2017, но не дал ожидаемого результата. Могут ли быть различия в используемом компиляторе?   -  person πάντα ῥεῖ    schedule 09.06.2017
comment
@πάνταῥεῖ Потому что в исправлениях, которые я нашел, были опечатки, не связанные с вашей проблемой. Некоторые из них сломали компиляцию. Некоторые из них выглядели как глупые ошибки (например, возврат копии карты), которые могли бы объяснить вашу проблему. С нулевой верой в то, что ваш код, вызывающий проблему, на самом деле является кодом, который вы разместили выше, я мог бы проголосовать за закрытие из-за опечатки, или код должен включать минимальный пример, но я не могу добросовестно ответить, что это работает для меня. Я до сих пор не знаю, является ли код, который вы тестируете, кодом выше. Поэтому просьба дать ссылку на живой пример.   -  person Yakk - Adam Nevraumont    schedule 09.06.2017
comment
@Yakk Я сейчас попробую ваш полностью исправленный пример с VS2017 ..   -  person πάντα ῥεῖ    schedule 09.06.2017
comment
@Yakk Да, это сработало, как и предполагалось. Большое спасибо. Я проверю ваши исправления на следующий рабочий день и применю все к производственному коду. Теперь снова удаляю вопрос.   -  person πάντα ῥεῖ    schedule 09.06.2017
comment
Возможно, дубликат stackoverflow.com/questions/14620842/   -  person ar2015    schedule 19.08.2018
comment
@ ar2015 Нет, это совершенно не связанная проблема.   -  person πάντα ῥεῖ    schedule 19.08.2018
comment
@πάνταῥεῖ, но я вижу, что это полностью связано.   -  person ar2015    schedule 19.08.2018
comment
@ ar2015 Я когда-либо знал, что вызов виртуальных методов из конструкторов не будет работать. CRTP — это статический полиморфизм.   -  person πάντα ῥεῖ    schedule 19.08.2018
comment
@πάνταῥεῖ, я думаю, вы еще не понимаете его концепции. Вы можете изучить stackoverflow .com/questions/2391679/   -  person ar2015    schedule 19.08.2018
comment
@ar2015 А? Что ты пытаешься мне сказать? Я хорошо понял, что означает виртуальный и статический полиморфизм и как его использовать с любыми интерфейсами. Я также знаю, как это работает на машинном уровне с различными языками программирования, такими как Java, C#, C или Delphi.   -  person πάντα ῥεῖ    schedule 19.08.2018
comment
@πάνταῥεῖ, Как вы думаете, чем вы отличаетесь от других?   -  person ar2015    schedule 19.08.2018
comment
Давайте продолжим обсуждение в чате.   -  person πάντα ῥεῖ    schedule 19.08.2018


Ответы (1)


Я решил повторно открыть вопрос и самостоятельно ответить на основе >Исправления Якка. Кажется, это более распространенная проблема (см. пока">здесь например)

Пример 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 в производных классах.

person πάντα ῥεῖ    schedule 16.06.2017