Функция Const, вызывающая non const или наоборот (во избежание дублирования)?
Есть ли какие-либо преимущества, связанные друг с другом:
class Foo
{
public:
const int& get() const
{
// stuff here
return myInt;
}
int& get()
{
return const_cast<int&>(static_cast<const Foo*>(this)->get());
}
};
или
class Foo
{
public:
int& get()
{
// stuff here
return myInt;
}
const int& get() const
{
return const_cast<Foo*>(this)->get();
}
};
Я использовал только первый, но я только что увидел, что второй использовал его, поэтому мне интересно.
Комментарий // stuff here
может быть нетривиальной проверкой, например, для получения индекса таблицы для возврата ссылки на элемент таблицы (например: myInt = myTable[myComputedIndex];
), поэтому я не могу просто сделать ее общедоступной. Таким образом, таблица и любой член не являются константами.
Ответы
Ответ 1
Если вам нужно создать функцию, которая является константной, и избегает дублирования, один простой способ сделать это делегирование реализации шаблону, например
class Foo {
private:
int my_int;
template <typename ThisPtr>
static auto& get(ThisPtr this_ptr) {
return this_ptr->my_int;
}
public:
int& get() {
return get(this);
}
const int& get() const {
return get(this);
}
};
Таким образом, вы свободны от страха, связанного с использованием const_cast
, mutable
и других вещей, которые пытаются уменьшить дублирование кода в таких случаях. Если у вас что-то не так, компилятор сообщит вам об этом.
Ответ 2
Игнорируя вопрос о том, действительно ли вам нужен геттер, лучшее решение, когда дублирование функций как в методе const
, так и не const
заключается в том, чтобы метод non const
вызывал метод const
и отбросить const
-ность результата (т.е. первую из двух альтернатив, которые вы представляете в вопросе).
Причина проста: если вы делаете это наоборот (с логикой в методе не const
), вы можете случайно изменить объект const
, и компилятор не поймает его во время компиляции (поскольку метод не объявлен const
) - это будет иметь поведение undefined.
Конечно, это только проблема, если "getter" на самом деле не является getter (т.е. если он делает что-то более сложное, чем просто возвращает ссылку на частное поле).
Кроме того, если вы не ограничены С++ 11, решение на основе шаблонов, представленное Curious в их ответе, является еще одним способом избежать этой проблемы.
Ответ 3
Есть ли какое-либо преимущество, использующее один над другим:...
Нет, оба плохо, потому что они нарушают принцип инкапсуляции данных.
В вашем примере вы должны сделать myInt
публичным членом.
Нет никакого преимущества, чтобы иметь геттеры для такого случая вообще.
Если вы действительно хотите (нужны) функции getter и setter, они должны выглядеть так:
class Foo
{
private:
mutable int myInt_;
// ^^^^^^^ Allows lazy initialization from within the const getter,
// simply omit that if you dont need it.
public:
void myInt(int value)
{
// Do other stuff ...
myInt = value;
// Do more stuff ...
}
const int& myInt() const
{
// Do other stuff ...
return myInt_;
}
}
Ответ 4
Вы не говорите, откуда приходит myInt
, от этого зависит лучший ответ.
Возможны 2 + 1 возможных сценария:
1) Наиболее распространенным случаем является то, что myInt
происходит от указателя, внутреннего к классу.
Предполагая, что это лучшее решение, которое позволяет избежать дублирования кода и кастинга.
class Foo{
int* myIntP;
...
int& get_impl() const{
... lots of code
return *myIntP; // even if Foo instance is const, *myInt is not
}
public:
int& get(){return get_impl();}
const int& get() const{return get_impl();}
};
Этот пример выше относится к массиву указателей и (большинству) интеллектуальным указателям.
2) Другой распространенный случай: myInt
ссылка или элемент значения, тогда предыдущее решение не работает.
Но это также случай, когда a getter
вообще не требуется.
Не используйте геттер в этом случае.
class Foo{
public:
int myInt; // or int& myInt;
};
сделано!:)
3) Существует третий сценарий, обозначенный @Aconcagua, это случай внутреннего фиксированного массива. В этом случае это подтасовка, это действительно зависит от того, что вы делаете, если найти индекс действительно проблема, то это можно учесть. Однако неясно, каково применение:
class Foo{
int myInts[32];
...
int complicated_index() const{...long code...}
public:
int& get(){return myInts[complicated_index()];}
const int& get() const{return myInts[complicated_index()];}
};
Моя точка зрения, понять проблему и не заниматься инженером. const_cast
или шаблоны не нужны для решения этой проблемы.
полный рабочий код ниже:
class Foo{
int* myIntP;
int& get_impl() const{
return *myIntP; // even if Foo instance is const, *myInt is not
}
public:
int& get(){return get_impl();}
const int& get() const{return get_impl();}
Foo() : myIntP(new int(0)){}
~Foo(){delete myIntP;}
};
#include<cassert>
int main(){
Foo f1;
f1.get() = 5;
assert( f1.get() == 5 );
Foo const f2;
// f2.get() = 5; // compile error
assert( f2.get() == 0 );
return 0;
}
Ответ 5
По мере того, как вы намереваетесь получить доступ к более сложным внутренним структурам (как это разъясняется посредством вашего редактирования, например, предоставление operator[](size_t index)
для внутренних массивов, как это делает std::vector), вам нужно убедиться, что вы не вызываете undefined путем изменения потенциального объекта const.
Риск этого выше во втором подходе:
int& get()
{
// stuff here: if you modify the object, the compiler won't warn you!
// but you will modify a const object, if the other getter is called on one!!!
return myInt;
}
В первом варианте вы можете быть в безопасности (если вы не делаете const_cast
здесь тоже, что сейчас действительно было бы плохо...), что является преимуществом такого подхода:
const int& get() const
{
// stuff here: you cannot modify by accident...
// if you try, the compiler will complain about
return myInt;
}
Если вам действительно нужно изменить объект в неконстантном геттере, вы не можете иметь общую реализацию в любом случае...
Ответ 6
Модификация объекта const с помощью неконстантного пути доступа [...] приводит к поведению undefined.
(Источник: http://en.cppreference.com/w/cpp/language/const_cast)
Это означает, что первая версия может привести к поведению undefined, если myInt
на самом деле является константным элементом Foo
:
class Foo
{
int const myInt;
public:
const int& get() const
{
return myInt;
}
int& get()
{
return const_cast<int&>(static_cast<const Foo*>(this)->get());
}
};
int main()
{
Foo f;
f.get() = 10; // this compiles, but it is undefined behavior
}
Вторая версия не будет компилироваться, потому что неконстантная версия get
будет плохо сформирована:
class Foo
{
int const myInt;
public:
int& get()
{
return myInt;
// this will not compile, you cannot return a const member
// from a non-const member function
}
const int& get() const
{
return const_cast<Foo*>(this)->get();
}
};
int main()
{
Foo f;
f.get() = 10; // get() is ill-formed, so this does not compile
}
Эта версия на самом деле рекомендована Скоттом Мейерсом в Effective С++ в разделе "Избегайте дублирования" в функции "const
и Non- const
Member".