Страницы

Поиск по вопросам

понедельник, 8 октября 2018 г.

Исключить дублирование кода в функциях с разной константностью

Рассмотрим такой код:
struct B {}; struct D1 : B {}; struct D2 : B {};
#define get if (s) return d1; else return d2;
volatile bool s;
struct C { const B& f() const { get } B& f() { get } private: D1 d1; D2 d2; };
Здесь видно, что разные версии f() должны возвращать один и тот же объект (в одном случае - константный, в другом - нет), основываясь на некоторой логике выбора, которая может быть достаточно сложной. В примере для исключения дублирования кода этой логики использована макроподстановка через #define.
Можно ли избежать дублирования кода в разных версиях f() не прибегая к услугам препроцессора?


Ответ

Проблема дублирования кода из-за соображений константности обычно встречается в двух вариантах:
На уровне отдельных функций: когда есть две функции с одинаковой реализацией, отличающиеся лишь константностью входных типов (в т.ч., как частный случай, константностью *this в методе класса) и соответствующей константностью возвращаемого значения.
В языке С одной из известных идиом для решения этой проблемы является написание одной-единственной функции, которая решает поставленную задачу в рамках соблюдения константности входных данных, а затем просто безусловно снимает константность с возвращаемого значения (см., например, стандартную функцию strstr). В данном случае предполагается, что вызывающий код, будучи в курсе ситуации с константностью данных, "по-джентельменски" вернет "потерянную" в процессе вызова функции константность на место.
В языке С++ этот подход технически тоже применим, но его использовать не принято. Точнее, традиционная С++ идиома, основанная внутренне фактически на том же самом подходе, внешне реализуется с небольшим отличием: полноценная реализация предоставляется для константной версии функции, а над ней надстраивается вторая - неконстантная - версия той же функции. Последняя реализуется через константную путем снятия константности с возвращаемого значения
const return_type *foo(const input_type *argument) { ... }
return_type *foo(input_type *argument) { return const_cast(foo(const_cast(argument)); }
Вот именно этот подход прекрасно подойдет в вашем случае. На уровне отдельных классов: кода надо реализовать два класса, которые фактически идентичны с точки зрения исходного кода, а отличаются лишь внешней константностью обрабатываемых данных. Хороший пример: константная и неконстантная версия класса контейнерного итератора.
В такой ситуации один из жизнеспособных подходов - реализация общей функциональности в виде шаблонного класса, параметризованного необходимым количеством типов (в простейшем случае - одним), и реализация требуемых финальных классов через специализации этого шаблона. Что-то вроде
template class list { template class iterator_impl { ... };
typedef iterator_impl iterator; typedef iterator_impl const_iterator; ... };
P.S. Ваш собственный ответ, использующий шаблонную функцию - это фактически адаптирование вышеприведенного второго подхода к первой ситуации. Работать, без сомнения, будет, однако именно в такой ситуации банальный вариант с const_cast, как мне кажется, выглядит проще и уместнее.

Комментариев нет:

Отправить комментарий