Можно ли делегировать инициализацию члена шаблонного класса клиенту?

#c #templates #design-patterns

#c #шаблоны #дизайн-шаблоны

Вопрос:

Я столкнулся со следующей проблемой: как инициализировать статический прокси, который содержит объект, предоставленный в качестве параметра шаблона.

Вот мой фрагмент кода, который показывает ситуацию:

 template<class SharedState>
  class TToolbar : public ImplCustomToolBar
  {
  public:
    virtual const ICustomToolbarsSharedState* const GetSharedState() const { return amp;sharedState_.Get();}

  private:
    // intent: static constructor for static SharedState initialization
    class SharedStateHolder
    {
    public:
      SharedStateHolder()
      {
        ToolbarsSharedState_.Initialize();
      }
      const SharedStateamp; Get() const { return ToolbarsSharedState_;}
    private:
      SharedState ToolbarsSharedState_;
    };
    static SharedStateHolder sharedState_;
  };

// I need TToolbar::SharedStateHolder sharedState_;
 

Поскольку неизвестно, какой параметр шаблона будет предоставлен, я не могу использовать явную специализацию шаблона для инициализации статического члена.

Итак, единственное решение, которое я нашел: клиенты TToolBar должны сами выполнять инициализацию. Но я думаю, что это подверженное ошибкам решение.

Вопрос: какие слабые места есть в моем решении и каков правильный способ справиться с такой ситуацией?

Комментарии:

1. Это проблема внедрения зависимостей, посмотрите, как std::vector::emplace ее решить, или общий шаблон .

2. @didierc О, большое вам спасибо, я никогда не слышал об этом шаблоне. Я знаю все шаблоны из Gof4 и подумал, что этого достаточно. Не могли бы вы дать мне несколько ссылок на другие шаблоны? Если вы опубликуете свой ответ, я мог бы принять его (конечно, после того, как я ознакомлюсь с шаблоном внедрения)

3. @didierc Я читал о внедрении зависимостей, я вспоминаю, что сталкивался с этим раньше. Как этот шаблон можно применить к моей проблеме?

Ответ №1:

Вы можете использовать статическую локальную переменную функции-члена:

 template<class SharedState>
class TToolbar : public ImplCustomToolBar
{
    virtual const ICustomToolbarsSharedState* const GetSharedState() const
    {
        static SharedStateHolder sharedState_;
        return amp;sharedState_.Get();
    }
};
 

Компилятор отвечает за то, чтобы статика была уникальной.

Или, если кто-то хочет избавиться от этого класса Holder:

 template<class SharedState>
class TToolbar : public ImplCustomToolBar
{
    virtual const ICustomToolbarsSharedState* const GetSharedState() const
    {
        static SharedState sharedState_;
        static bool initialized = false;
        if (!initialized)
        {
            sharedState_.Initialize();
            initalized = true;
        }
        return amp;sharedState_;
    }
};
 

Комментарии:

1. Спасибо. Я рассматривал такие решения. Мне нравится первый, я думаю, что он отвечает на часть моего вопроса «правильный способ решения такой ситуации», но оставшийся вопрос все еще не раскрыт (и это самое важное для меня). 1 Но я не могу принять ваш ответ.

Ответ №2:

Один из способов решить вашу проблему — делегировать создание TToolBar factory , который знает об этом общем состоянии (по сути, это то, что SharedStateHolder делает), и передать его защищенному конструктору of TToolBar , который может использовать только эта фабрика, будучи friend с. TToolBar Объекты, нуждающиеся в такой панели инструментов, должны пройти через фабрику, чтобы получить ее.

 template<typename SharedState>
class TToolBar {
    friend class TToolBarFactory;
    // ...
    protected:
        TToolBar(SharedState *shared){
            // ...
        }
        // all other constructor also protected
};

class TToolBarFactory{
public:
       TToolBarFactory();
       TToolBar<SharedStateImpl> *makeToolBar();
        // other toolbar pseudo constructors
};
 

Теперь SharedState построение делегируется выделенному классу, который должен быть одноэлементным, чтобы гарантировать, что все панели инструментов имеют одинаковое состояние.

В качестве альтернативы, поскольку SharedState экземпляр является статическим, вы можете просто создать статический метод для установки этой переменной один раз для всех и заставить фабрику вызывать ее при создании экземпляра. Тогда вам не нужно было бы передавать его TToolBar конструктору (как я сделал выше) и разрешать другим объектам создавать их напрямую.

Мы можем рассматривать TToolBarFactory как конструктор для TToolBar класса, а не его экземпляров.

Обратите внимание, что эта проблема ортогональна тому факту, который TToolBar параметризован на SharedState : вы могли бы заставить фабрику напрямую предоставлять специализированные экземпляры TToolBar (как я сделал) или обобщить его на шаблон с конструктором a la std::vector::emplace , который шаблонируется на основе содержащихся параметров конструктора данных.

 template <typename SharedState>
class TToolBarFactory {
public:
    template <class... args>
    TToolBarFactory(Args...args){
        TToolBar<SharedState>::setSharedState(new SharedState(args...));
        // remaining factory init code.
    }

    // this could also be a variadic templated function
    TToolBar<SharedState> *makeToolBar(){
        return new TToolBar<SharedState>();
    }
};

// example 

class SharedStateImpl {
public:
    SharedStateImpl(int i, float f){}
};

// the factory instance
TToolBarFactory<SharedStateImpl> factory(42,3.14);

int main(){
    TToolBar<SharedStateImpl> *toobar = factory.makeToolBar();
}
 

Если я понял вашу проблему, на самом деле речь идет о том, что она была SharedState создана снаружи TToolBar , потому что.вы не хотите, чтобы этот класс делал какие-либо предположения о SharedState конструкторах. Мое решение немного тяжеловесно. Вы определенно могли бы сделать то же самое, просто создав SharedStateHolder параметр шаблона TToolBar и сохранив там все детали инициализации.

Комментарии:

1. спасибо, у меня возникла идея о внедрении зависимостей с помощью конструктора, я думаю, что ваше решение представляет некоторую чрезмерную сложность. Моя цель — уменьшить сложность. Спасибо за хорошее объяснение! 1 за ответ, но я не могу его принять, поскольку он лишь частично отвечает на мой вопрос (проверьте заголовок вопроса)

2. Да, я понял, что немного увлекся, особенно после того, как увидел другой ответ. Смотрите последний абзац моего ответа.

3. это моя ошибка, я пропустил слово static в заголовке вопроса, я имел в виду «Может ли инициализация статического члена шаблонного класса быть делегирована клиенту». Итак, ваш ответ решил вопрос. Спасибо!