Рассмотрим такой код:
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
Вот именно этот подход прекрасно подойдет в вашем случае.
На уровне отдельных классов: кода надо реализовать два класса, которые фактически идентичны с точки зрения исходного кода, а отличаются лишь внешней константностью обрабатываемых данных. Хороший пример: константная и неконстантная версия класса контейнерного итератора.
В такой ситуации один из жизнеспособных подходов - реализация общей функциональности в виде шаблонного класса, параметризованного необходимым количеством типов (в простейшем случае - одним), и реализация требуемых финальных классов через специализации этого шаблона. Что-то вроде
template
typedef iterator_impl
P.S. Ваш собственный ответ, использующий шаблонную функцию - это фактически адаптирование вышеприведенного второго подхода к первой ситуации. Работать, без сомнения, будет, однако именно в такой ситуации банальный вариант с const_cast, как мне кажется, выглядит проще и уместнее.
Комментариев нет:
Отправить комментарий