Является ли CURLY Recurring Template Pattern (CRTP) правильным решением здесь? - PullRequest
0 голосов
/ 10 января 2019

Сценарий

Рассмотрим класс Logger, который имеет функцию-член write(), перегруженную для стандартных типов C ++, а также имеет несколько удобных шаблонов функций, таких как writeLine(), которые внутренне вызывают write():

class Logger {
  public:
    void write(int x) { ... }
    void write(double x) { ... }
    ...

    template <typename T>
    void writeLine(T x) { write(x); ... }
    ...
};

Рассмотрим далее подкласс FooLogger, который добавляет дополнительные write() перегрузки для типов, специфичных для домена (назовем два из них FooType1 и FooType2):

class FooLogger : public Logger {
  public:
    using Logger::write;

    void write(FooType1 x) { ... }
    void write(FooType2 x) { ... }
    ...
};

( автономный пример программы на Ideone )

Проблема

FooLogger::write() при прямом вызове теперь поддерживает любой аргумент, для которого либо из двух классов обеспечивает перегрузку.

Однако FooLogger::writeLine() поддерживает только те типы аргументов, для которых класс Logger имеет перегрузку write() ... он не видит дополнительных перегрузок write(), объявленных в классе FooLogger.

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

Текущее решение

Я заставил его работать, используя шаблон CURLYURURRING Template (CRTP):

template <typename TDerivedClass>
class AbstractLogger {
    ...

    template <typename T>
    void writeLine(T x) { static_cast<TDerivedClass*>(this)->write(x); ... }
};

class Logger : AbstractLogger {}


class FooLogger : public AbstractLogger<FooLogger> {
    ...
};

( автономный пример программы на Ideone )

Хотя он и выполняет свою работу, он достигается за счет увеличения сложности и скорости кода:

  1. Это сделало реализацию базового класса значительно труднее для чтения (см. Ссылку на Ideone) и сложнее поддерживать (не забывайте танцевать static_cast везде, где это необходимо, при добавлении большего количества кода в класс в будущем !)
  2. Требуется разделить AbstractLogger и Logger на два класса.
  3. Поскольку базовый класс теперь является шаблоном класса, реализации всех его функций-членов должны теперь быть включены в заголовок (а не в файл .cpp) - даже те, которые не должны выполнять static_cast вещь.

Вопрос

Учитывая вышесказанное, я ищу информацию от людей с опытом работы с C ++:

  • Является ли CRTP подходящим инструментом для этой работы?
  • Есть ли другой способ решить эту проблему?

Ответы [ 2 ]

0 голосов
/ 10 января 2019

Как насчет другого пути:

template <typename ...Ts>
class Logger : private Ts...
{
public:
    using Ts::write...;

    void write(int x) { /*...*/ }
    void write(double x) { /*...*/ }
    // ...

    template <typename T>
    void writeLine(T x) { write(x); /*...*/ }
    // ...
};

class FooWriter
{
public:
    void write(FooType1 x) { /*...*/ }
    void write(FooType2 x) { /*...*/ }
};
using FooLogger = Logger<FooWriter>;

А затем используйте любой из (или их псевдонимы):

Logger<> или Logger<FooWriter> или Logger<FooWriter, BarWriter> ...

0 голосов
/ 10 января 2019

Почему бы не использовать бесплатные функции, например, operator<<, определенные для вашего типа и типа вывода потока логгера, или просто функции, которые вызываются, если они видны? Для примера того, как это сделать: googletest написан так, что все утверждения могут быть настроены таким образом с помощью методов сериализации. См. Обучение Googletest, как печатать свои значения , а затем вы можете посмотреть в реализации, как они это делают.

(Обратите внимание, что у googletest тоже есть методы: вы можете предоставить метод PrintTo() в своем классе или перегрузить operator<<, предпочитая PrintTo(), если оба доступны. Преимущество заключается в том, что вы можете сериализоваться для регистрации иначе , чем сериализация в типичные выходные потоки (например, у вас уже есть operator<< для вашего класса, который не делает то, что вы хотите для журналов).

(Волшебство содержится в gtest-printer.h - см. class UniversalPrinter в строке 685 для триггера.)

Это также является преимуществом, заключающимся в том, что очень легко добавить любой класс / структуру / объект для правильной регистрации, даже не беспокоясь о расширении класса регистрации. Более того ... что произойдет, если кто-то расширит класс логгера (то есть унаследует его) для сериализации класса AAA, а в другом фрагменте кода будет другой деривация для сериализации класса BBB, а затем, наконец, вы напишете несколько код, в который вы хотите войти и AAA с и BBB с? Подход к производному классу там работает не так хорошо ...

...