Удалить тип из функции с возвращаемым значением шаблона

У меня есть следующие классы:

class A {
public:
  virtual std::string Serialize();
  virtual void Deserialize(std::string);

  template <typename T>
  T* Clone()
  {
    std::string s = Serialize();
    T* t = new T();
    t->Deserialize(s);
    return t;
  }
};

class B : public A {
public:
  std::string Serialize() { ... }
  void Deserialize(std::string) { ... }
};

Теперь, если я хочу клонировать B, я делаю следующее:

B b1;
B* b2 = b1.Clone<B>();

Есть ли способ удалить тип шаблона без повторной реализации Clone в каждом производном классе?

Мне нужно что-то вроде этого:

B b1;
B* b2 = b1.Clone();

Ответы

Ответ 1

Способ сделать это с помощью CRTP:

class A {
public:
    virtual std::string Serialize();
    virtual void Deserialize(std::string);
    virtual A* Clone() = 0;  
};

template <class T>
class HelperA : public A {

    T* Clone() override
        {
            std::string s = Serialize();
            T* t = new T();
            t->Deserialize(s);
            return t;
        }
};

class B : public HelperA<B> {
public:
    std::string Serialize() { ... }
    void Deserialize(std::string) { ... }
};

Эти иерархии уровня 3 довольно распространены. В принципе, верхний класс - чистый интерфейс, как и раньше (обратите внимание: вы должны = 0 другие функции тоже). Средний класс использует CRTP-шаблон: он исчисляется на основе типизированного. идея состоит в том, что статический доступ к производному типу может автоматически реализовывать такие вещи, как Clone. Тогда производный тип реализует любую реализацию, которая не может быть выполнена в общем случае.

Обратите внимание: наследуемый тип наследуется от класса CRTP. То, откуда происходит название (Curiously Recurring Template Pattern). Конечно, так как наследование транзитивно, B также наследует от A все еще, как изначально, позволяя такие же вещи.

Вот полный рабочий пример, который вы можете выполнить: http://coliru.stacked-crooked.com/a/8f2b201a06b5abcc. Я сохранил код в ответе как можно более похожий на вопрос, но в примере coliru есть несколько небольших, но важных отличий:

  • использование указателей владения вместо необработанных указателей считается хорошей практикой в ​​С++, а поскольку интеллектуальные указатели не ковариантны, это влияет на подписи
  • правильное использование = 0 и переопределение, а также const
  • пример статического downcast, который является своего рода сигнатурой CRTP, которая не придумала ваш пример