Как написать входной фильтр, который является благоприятным для политики?

Фон:
Наше программное обеспечение использует несколько API для ввода / вывода файлов: FILE*, CStdio (и несколько производных), HANDLE...

Я написал FilePointer Обертка RAII для FILE*, который послужил нам хорошей заменой всего существующего кода на Си.

Более новый код обычно использовал производный от CStdio или обернутый класс.

Недавно я написал SimpleTextFile для обработки ввода / вывода UTF-16LE, в дополнение к MBCS наших предыдущих версий.

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

И вот тут-то и возникает проблема - если я смешал строку-считыватель с фильтром, любые алгоритмы, которым он передается, больше не могут использовать классы политики, чтобы выяснить, как адаптироваться к базовому типу (потому что R сейчас Wrapper<R> и никакой политики для Wrapper<R>).

Вопрос:
Как я могу создать смешанные классы шаблонов, которые могут обеспечить новое поведение для существующего типа, в то же время позволяя различным политикам, которые работали с базовым типом, продолжать работать?

Подробности:
шаблон политики:
StreamPositionPolicy<T> - предоставляет GetPosition() и SetPosition(), адаптированные к T. LineReaderPolicy<T> - предоставляет общий набор интерфейсов для чтения строки из T. FileNamePolicy<T> - предоставляет GetFilename() для T.

Таким образом, если T является производной от CStdio или FILE*, вышеприведенное сделает все возможное, чтобы предоставить общий интерфейс для поиска, чтения строк и получения исходного имени файла.

Дополнительно я имею:
FilteredStringReader<F,R> который женится на фильтре для читателя. Ранее я делал это как:

template <typename Filter, typename Reader>
class FilteredStringReader
{
    Filter      m_filter;
    Reader &    m_reader;

public:

// Constructors
    FilteredStringReader(
        Filter      filter,
        Reader &    reader
    ) :
        m_filter(filter),
        m_reader(reader)
    {
    }

    bool ReadString(CString & strLine)
    {
        return ReadFilteredString(m_reader, m_filter, strLine);
    }
};

Это хорошо работает для любых алгоритмов, которые используют LineReaderPolicy<>, потому что политика по умолчанию состоит в том, чтобы пытаться использовать интерфейс ReadString(), и этот интерфейс соответствует политике (общей) по умолчанию, и жизнь хороша.

Тем не менее, если этот объект передается в один из алгоритмов, который должен использовать одну из других политик - например, StreamPositionPolicy<FilteredStringReader<F,R>> то эта схема рушится! Там нет StreamPositionPolicy<> для FilteredStringReader<> и FilteredStringReader<> не подходит по умолчанию StreamPositionPolicy<> (он обеспечивает только интерфейс чтения строки, а не интерфейс потока или интерфейс имени и т. д.)

Итак, я подумал, что такой миксин, вероятно, должен использовать CRTP и наследовать его базовый тип файла / тип чтения. Тогда это будет один из них, и любые классы политик, которые имеют специализации для основного читателя, будут успешными.

Но это поднимает проблемы времени жизни / владения / копирования:

template <typename Filter, typename Reader>
class FilteredStringReader : public Reader
{
    Filter      m_filter;

public:

// Constructors
    FilteredStringReader(
        Filter      filter,
        Reader &    reader
    )
        : Reader(reader)
        , m_filter(filter)
    {
    }

    bool ReadString(CString & strLine)
    {
        return ReadFilteredString(m_reader, m_filter, strLine);
    }
};

Удивительно, но этот вид работ - создание этого объекта политики возможно... но он копирует экземпляр считывателя (что может не быть большой идеей, в зависимости от реализации читателя - или, более вероятно, - некоторые типы читателей просто выиграли " разрешить копию).

Мне нужен только один экземпляр моего объекта для чтения - тот, который обернут экземпляром шаблона mixin, и ничего более.

Итак, я чувствую, что это идет по неверному пути.

Я могу использовать различные шаблоны и, возможно, использовать идеальную переадресацию, чтобы моя конструкция mixin сама по себе + ее основа была на месте. Но это теряет часть функциональности предыдущего воплощения: оригинальная версия FilteredStringReader<F,R> показанный может быть наложен поверх читателя, использован, а затем отброшен, в то время как время жизни самого считывателя продолжалось (или было обернуто более глубоко для целей другого алгоритма).

Таким образом, использование CRTP кажется плохим выбором. Но потом я возвращаюсь к первоначальной проблеме, как сделать оболочки для типа R, который перехватывает только один интерфейс, оставляя все остальные в покое?

1 ответ

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

template <typename Filter, typename Reader>
class FileNamePolicy<FilteredStringReader<Filter, Reader>>: public FileNamePolicy<Reader> {};

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

template <typename Filter, typename Reader>
class FilteredStringReader
{
    Filter      m_filter;
    Reader &    m_reader;

public:
    operator Reader &() { return m_reader; }
    operator const Reader &() const { return m_reader; }
    // ...
};
Другие вопросы по тегам