Код шаблона в C++

94
19 ноября 2021, 15:00

Существует ли способ построить в классе группу "одинаковых" методов, но использующих каждый одно уникальное свойство/параметр, при помощи шаблона (template) или других техник, чтобы не писать определения для каждого "типового" метода?

Поясню примером - ниже код (используемый в блоке шаблона проектирования "наблюдатель"), который хотелось-бы упростить/сократить при помощи использования "template" (или других методов) чтобы уменьшить вероятность возможных ошибок при увеличении количества и функционала наблюдателей:

class MyService {
  viod add_f0_observer(MyType& ref);
  viod add_f1_observer(MyType& ref);
  viod add_f2_observer(MyType& ref);
  void event_caller();
  MyType* f0_observer;
  MyType* f1_observer;
  MyType* f2_observer;
}
viod MyService::add_f0_observer(MyType& ref) {
    f0_observer = &ref;
    // ...
}
viod MyService::add_f1_observer(MyType& ref) {
    f1_observer = &ref;
    // ...
}
viod MyService::add_f2_observer(MyType& ref) {
    f2_observer = &ref;
    // ...
}
Answer 1

В вашем конкретном примере общий код и так прост до предела, поэтому еще больше "упростить" его не получится. Но если предположить, что в реальном коде общий код будет существенно более объемным, отличаясь в трех своих вариантом только использованием поля f0_observer, f1_observer или f2_observer, то возможность упрощения есть.

Эту задачу можно решить без шаблонов, объявив "универсальную" функцию с обычным параметром типа указатель-на-член-класса

class MyService 
{
  void add_observer(MyType *MyService::*field, MyType& ref)
  { 
    // ...
    this->*field = &ref; 
    // ...
  }
  void add_f0_observer(MyType& ref)
    { add_observer(&MyService::f0_observer, ref); }
  void add_f1_observer(MyType& ref)
    { add_observer(&MyService::f1_observer, ref); }
  void add_f2_observer(MyType& ref)
    { add_observer(&MyService::f2_observer, ref); }
  MyType* f0_observer;
  MyType* f1_observer;
  MyType* f2_observer;
};

Понятно, что в данном примере и указатель-на-член-класса тут не обязателен: можно было бы в качестве первого параметра использовать обычную ссылку или указатель на MyType. Но я специально использовал именно указатель-на-член-класса, ибо он в таком случае будет являться константой времени компиляции.

Если учесть, что такой указатель является константой времени компиляции, можно превратить его из обычного параметра в шаблонный параметр

class MyService 
{
  template <MyType *MyService::*field> void add_observer(MyType& ref)
  { 
    // ...
    this->*field = &ref; 
    // ...
  }
  void add_f0_observer(MyType& ref)
    { add_observer<&MyService::f0_observer>(ref); }
  void add_f1_observer(MyType& ref)
    { add_observer<&MyService::f1_observer>(ref); }
  void add_f2_observer(MyType& ref)
    { add_observer<&MyService::f2_observer>(ref); }
  MyType* f0_observer;
  MyType* f1_observer;
  MyType* f2_observer;
};

Стоит ли это делать - вопрос отдельный.

Ту же самую идею можно реализовать не только через голые указатели, но и через более высокоуровневые средства языка: std::bind или лямбды.

Answer 2

Я может и не очень понел ваши намерения, поскольку логично в классе просто хранить контейнер и каждый раз в нем просто добавлять элемент. Но, насколько я понел что вы хотите сделать, напишу пример:

template<size_t Tab, class Seq = std::vector< MyType*>> 
class MyService {
    //хранит контейнер нулевых указателей
    Seq observers = Seq(Tab, nullptr);
    //...
public: 
    template<size_t k>
    void add_observer(MyType& ref) 
    {
        //каким нибудь образом не позволить
        // выход за пределы контейнера
        if constexpr (k >= Tab)
            observers[Tab - 1] = &ref;
        else
            observers[k] = &ref;
    }
    //...   
};
//и если вам нужен класс с тремя указательями
//то инстанцируйте этот класс один раз
template class MyService<3>; //пользуется вектором
//дальше можете использовать например так:
void add(MyService<3> &s, std::array<MyType, 3> arr)
{
    /*инстанцируем 3 функции_члена, каждая из которых
    будет работать с соответствующим указателем*/       
    s.add_observer<0>(arr[0]);
    s.add_observer<1>(arr[1]);
    s.add_observer<2>(arr[2]);  // или
    s.add_observer<5>(arr[2]);
    //...
}
Answer 3

Есть вариант с вынесением общей функциональности в шаблонную функцию, а конкретных реализаций - в перегруженную. Например:

class MyService{
public:
    struct f0_observer{ static constexpr int id = 0;};
    struct f1_observer{ static constexpr int id = 1;};
    struct f2_observer{ static constexpr int id = 2;};
    template<class O> auto add_observer(O type)->decltype(process(type)){
        observers[type.id] = &ref;
        /// Вместо type.id можно реализовать шаблонный список типов, позволяющий извлечь id по типу
        process(type);
    }
private:
    void process(f0_observer){ /*...*/} 
    void process(f1_observer){ /*...*/} 
    void process(f2_observer){ /*...*/} 
    std::array<MyType*> observers
}
void foo(){
   MyService s;
   MyType t;
   s.add_observer(MyService::f1_observer{}, &t);
   /*...*/   
}

Для данного конкретного случая это явно не оправдано.

READ ALSO
Как реализовать решение задачи ЕГЭ на C++?

Как реализовать решение задачи ЕГЭ на C++?

Возник такой вопрос: как реализовать решение следующей задачи на C++? Вводятся число N - количество строкВ каждой из N строк через пробел вводятся...

333
Как повысить привилегии приложения (UAC)

Как повысить привилегии приложения (UAC)

Делаю приложение, которое запускается на Windows 7/10 от пользователя с правами администратораПри этом запускается оно в обычном режиме (с ограниченными...

178
Почему std::vector.push_back(new struct()) работает медленнее чем std::vector.push_back(struct_item)

Почему std::vector.push_back(new struct()) работает медленнее чем std::vector.push_back(struct_item)

хочу хранить в векторе некоторые структуры,(много)Интересует вопрос почему такой код работает быстрее

191
Ошибка при трансяции потока через хромкаст cast_sender.js

Ошибка при трансяции потока через хромкаст cast_sender.js

Добавляю хромкаст согласно туториалу

188