Предполагая, что вы принимаете константность как технику, я думаю, это означает, что вы предпочитаете константность, проверенную компилятором, краткости. Итак, вы хотите, чтобы компилятор проверил две вещи:
- То, что "this" не является константным, вызывающая сторона может безопасно изменить референт возвращаемого вами указателя.
- То, что когда "this" является const, любая выполняемая вами работа не выполняет никаких неконстантных операций над "this".
Если константная версия вызывает неконстантную, то вы не получите (2). Если неконстантная версия вызывает константную и const_casts результат, то вы не получите (1). Например, предположим, что Bar
на самом деле char
, и код, который вы пишете, в конечном итоге возвращает (в некоторых случаях) строковый литерал. Это скомпилирует (а -Wwrite-strings не выдает предупреждения), но ваш вызывающий в итоге получит неконстантный указатель на строковый литерал. Это противоречит «вы предпочитаете проверенную компилятором константность».
Если они оба вызывают вспомогательную функцию-член Bar *getBar() const
, тогда вы получаете оба (1) и (2). Но если возможно написать эту вспомогательную функцию, то почему вы вообще возитесь с константной и неконстантной версиями, когда вполне нормально модифицировать Bar, возвращаемый из const Foo? Иногда, возможно, некоторые детали реализации означают, что вы реализуете интерфейс с двумя средствами доступа, даже если вам нужен только один. В противном случае либо помощник не может быть записан, либо две функции могут быть заменены только одним помощником.
До тех пор, пока размер кода не имеет значения, я думаю, что лучший способ достичь и (1), и (2) - это заставить компилятор фактически рассмотреть оба случая:
struct Bar { int a; };
struct Foo {
Bar *bar() { return getBar<Bar>(this); }
const Bar *bar() const { return getBar<const Bar>(this); }
Bar *bar2() const { return getBar<Bar>(this); } // doesn't compile. Good.
Bar *bar3() const { return getBar<const Bar>(this); } // likewise
private:
template <typename B, typename F>
static B *getBar(F *self) {
// non-trivial code, which can safely call other functions with
// const/non-const overloads, and we don't have to manually figure out
// whether it's safe to const_cast the result.
return &self->myBar;
}
Bar myBar;
};
Если код тривиален, как operator[]
, который обращается к некоторому массиву, принадлежащему объекту, тогда я просто продублирую код. В какой-то момент вышеприведенный шаблон функции требует меньше усилий по написанию кода, чем дублирование, и в этот момент используется шаблон.
Я думаю, что подход const_cast, хотя и умный и, казалось бы, стандартный, просто бесполезен, потому что он выбирает краткость вместо проверенной компилятором константности. Если код в методе тривиален, вы можете скопировать его. Если это не тривиально, вам или сопровождающему вас не легко увидеть, что const_cast действительно действителен.