Интерфейсный класс с перегрузками методов? - PullRequest
0 голосов
/ 17 мая 2018

Предположим, у меня есть несколько типов

class A {…};
class B {…};
class C {…};
...

И у меня есть некоторые другие классы, которые делают разные вещи над объектами этих типов

class process1
{
public:
    void do_stuff(std::vector<A>& …);
};

class process2
{
public:
    void do_stuff(std::vector<B>& …);
    void do_stuff(std::vector<C>& …);
};

...

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

template <class T>
class process_interface
{
public:
    virtual void do_stuff(std::vector<T>& …)
    {
        // Default implementation.
    }

    // I might also want this.
    // virtual void do_other_stuff(std::vector<T>& …) = 0;
};

и пусть process1 и process2 происходят из него. Таким образом, я говорю, что все классы «процесса» должны иметь do_stuff(), и что не обязательно писать это явно, потому что есть версия по умолчанию.

Но, конечно, это не работает, потому что process2 имеет 2 версии do_stuff(). (У некоторых других «процессных» классов может быть больше.) Так есть ли способ достичь того, чего я хочу, с помощью этого «интерфейсного» класса / шаблона? Я думал об использовании шаблона с переменным числом, но я полагаю, что это тоже не сработает.

А теперь вот что у меня есть:

class process_interface
{
public:
    template <class T>
    void do_stuff(std::vector<T>& …)
    {
        // Default implementation.
    }

    // template <class T>
    // void do_other_stuff(std::vector<T>& …)
    // {
    //     throw std::runtime_error("Nope.");
    // }
};

Но у него есть проблемы. Например, do_stuff() не может быть виртуальным / чисто виртуальным. И если у меня есть другие методы, которые требуют такой же обработки, например, do_other_stuff() там, нет никакого способа заставить подклассы иметь do_other_stuff() для каждого типа, на который можно do_stuff() включить.

Ответы [ 3 ]

0 голосов
/ 17 мая 2018

Может быть, вы можете сделать process2 (и все классы, которым нужно больше do_stuff(), своего рода вариационный самонаследующий класс.

Что-то как

template <typename...>
struct process2;

template <typename T>
struct process2<T> : public process_interface<T>
 { };


template <typename T0, typename ... Ts>
struct process2<T0, Ts...> : public process_interface<T0>,
                             public process2<Ts...>
 {
   using process_interface<T0>::do_stuff;
   using process2<Ts...>::do_stuff;
 };

Ниже приводится полный рабочий пример

#include <vector>

class A {};
class B {};
class C {};
class D {};

template <typename T>
struct process_interface
 {
   virtual void do_stuff (std::vector<T> const &)
    { }
 };

struct process1 : public process_interface<A>
 { };

template <typename...>
struct process2;

template <typename T>
struct process2<T> : public process_interface<T>
 { };


template <typename T0, typename ... Ts>
struct process2<T0, Ts...> : public process_interface<T0>,
                             public process2<Ts...>
 {
   using process_interface<T0>::do_stuff;
   using process2<Ts...>::do_stuff;
 };

int main ()
 {
   process1           p1;
   process2<B, C, D>  p2;

   p1.do_stuff(std::vector<A>{});
   p2.do_stuff(std::vector<B>{});
   p2.do_stuff(std::vector<C>{});
   p2.do_stuff(std::vector<D>{});
 }

Начиная с C ++ 17, нет необходимости в саморекурсивном process2: вы можете использовать распаковку using и process2 просто стать

template <typename ... Ts>
struct process2 : public process_interface<Ts>...
 {
   using process_interface<Ts>::do_stuff ... ; 
 };
0 голосов
/ 17 мая 2018

Используйте вашу первую версию и используйте множественное наследование:

class process2 : process_interface<B>, process_interface<C> {
public:
    using process_interface<B>::do_stuff;
    using process_interface<C>::do_stuff;
};

Обратите внимание, что объявления использования приводят do_stuff в базовых классах к самому process2, поэтому оба могут быть найдены путем поиска по имени, а затем участвуют в разрешении перегрузки.

Полный пример

0 голосов
/ 17 мая 2018

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

Вот супер простой способ получить то, что вы хотите без какого-либо наследства:

class common_interface {
   template <class T>
   void do_stuff(std::vector<T>& …)
   {
     //...
   }
};

class process_1 {
  common_interface common_;
public:
   void do_stuff(std::vector<A>& v) {
     common_.do_stuff(v);
     //...
   }
};

class process_2 {
  common_interface common_;
public:
   void do_stuff(std::vector<B>& v) {
     common_.do_stuff(v);
     //...
   }

  void do_stuff(std::vector<C>& v) {
     common_.do_stuff(v);
     //...
  }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...