Чтобы избежать нетривиального дублирования, связанного с константой в C ++, существуют ли случаи, когда const_cast будет работать, но частная константная функция, возвращающая non-const, не будет?
В пункте 3 « Эффективного C ++» Скотта Мейерса он предполагает, что const_cast в сочетании со статическим приведением может быть эффективным и безопасным способом избежать дублирования кода, например
const void* Bar::bar(int i) const
{
...
return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}
Далее Майерс объясняет, что вызывать функцию const неконстантной функцией опасно.
Код ниже является контрпримером, показывающим:
- вопреки предложению Мейерса, иногда const_cast в сочетании со статическим приведением является опасным
- иногда вызывать функцию const неконстантно менее опасно
- иногда оба способа использования const_cast скрывают потенциально полезные ошибки компилятора
- Избегание const_cast и наличие дополнительного закрытого члена const, возвращающего неконстантный, является еще одним вариантом
Является ли любая из стратегий const_cast предотвращением дублирования кода хорошей практикой? Вы бы предпочли стратегию частного метода вместо этого? Есть ли случаи, когда const_cast будет работать, а приватный метод - нет? Есть ли другие варианты (кроме дублирования)?
Моя забота о стратегиях const_cast заключается в том, что даже если код является правильным при написании, позже во время обслуживания код может стать неправильным, и const_cast будет скрывать полезную ошибку компилятора. Кажется, что обычная частная функция, как правило, безопаснее.
class Foo
{
public:
Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
: mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
{}
// case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to
// const_cast prevents a useful compiler error
const LongLived& GetA1() const { return mConstLongLived; }
LongLived& GetA1()
{
return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
}
/* gives useful compiler error
LongLived& GetA2()
{
return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
}
const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
*/
// case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:
int GetB0(int i) { return mCache.Nth(i); }
int GetB0(int i) const { return Fibonachi().Nth(i); }
/* gives useful compiler error
int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
int GetB1(int i)
{
return static_cast<const Foo*>(this)->GetB1(i);
}*/
// const_cast prevents a useful compiler error
int GetB2(int i) { return mCache.Nth(i); }
int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }
// case C: calling a private const member that returns non-const seems like generally the way to go
LongLived& GetC1() { return GetC1Private(); }
const LongLived& GetC1() const { return GetC1Private(); }
private:
LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }
const LongLived& mConstLongLived;
LongLived& mMutableLongLived;
Fibonachi mCache;
};
class Fibonachi
{
public:
Fibonachi()
{
mCache.push_back(0);
mCache.push_back(1);
}
int Nth(int n)
{
for (int i=mCache.size(); i <= n; ++i)
{
mCache.push_back(mCache[i-1] + mCache[i-2]);
}
return mCache[n];
}
int Nth(int n) const
{
return n < mCache.size() ? mCache[n] : -1;
}
private:
std::vector<int> mCache;
};
class LongLived {};
Ответы:
При реализации константных и неконстантных функций-членов, которые различаются только в зависимости от того, является ли возвращенный ptr / reference константой, лучшая стратегия DRY заключается в следующем:
например
Давайте назовем это частной константной функцией, возвращающей неконстантный шаблон .
Это лучшая стратегия, позволяющая избегать дублирования прямым способом, при этом позволяя компилятору выполнять потенциально полезные проверки и сообщать о связанных с ними сообщениях об ошибках.
источник
const
экземпляра (если только ссылка не на что-то объявленаmutable
, или если вы не используете a,const_cast
но в обоих случаях нет проблем с самого начала ). Кроме того, я не смог найти что - нибудь на «частную функции сопза возвращающейся неконстантную шаблон» (если она должна была быть шутки назвать модель .... оно не смешно;)Да, вы правы: многие программы на C ++, которые пытаются использовать const -корректность, резко нарушают принцип DRY, и даже закрытый член, возвращающий non-const, слишком сложен для удобства.
Тем не менее, вы упускаете одно наблюдение: дублирование кода из-за правильности констант является проблемой, только если вы предоставляете другим членам доступ к коду данных. Это само по себе является нарушением инкапсуляции. Обычно такой тип дублирования кода встречается в основном в простых средствах доступа (в конце концов, вы передаете доступ к уже существующим элементам, возвращаемое значение обычно не является результатом вычисления).
Мой опыт показывает, что хорошие абстракции не включают в себя аксессоры. Следовательно, я в значительной степени избегаю этой проблемы, определяя функции-члены, которые действительно что-то делают, а не просто предоставляя доступ к элементам данных; Я пытаюсь смоделировать поведение вместо данных. Мое главное намерение в этом состоит в том, чтобы фактически получить некоторую абстракцию как от моих классов, так и от их отдельных функций-членов, вместо того, чтобы просто использовать мои объекты в качестве контейнеров данных. Но этот стиль также весьма успешен в том, чтобы избежать множества постоянных / неконстантных повторяющихся однострочных аксессоров, которые так распространены в большинстве кодов.
источник