Как написать входной фильтр, который является благоприятным для политики?
Фон:
Наше программное обеспечение использует несколько 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; }
// ...
};