Подтвердить что ты не робот

Как предотвратить случайное вызов мутирующей функции на неконстантный объект?

Предположим, что у нас есть объект obj типа myType, и мы хотели бы передать его функции Foo, которая возвращает нам некоторую ценную информацию об obj. function Bar - это где obj объявлено и из которого Foo вызывается следующим образом:

void Bar ()
{

myType obj; //default constructor

string valuableInfo = Foo(obj); 

//do other stuff
//...

} //end of Bar()

Этот фрагмент кода, конечно, мало говорит о том, принимает ли Foo obj как ссылку или как значение, и каким образом Foo изменяет obj каким-либо образом.

конечно, если Foo принимает obj как значение или константу ссылки, у нас не будет никаких проблем.

string Foo (const myType & input); //this is fine 
string Foo (myType input); //so is this

но мы этого не гарантируем! сигнатура функции вполне может быть

string Foo (myType & input); //asking for trouble!!

но ужасно неудобно проверять подпись каждой функции, которую мы хотели бы передать obj, поэтому как мы можем указать, что мы хотим передать наш объект только тем функциям, которые обещают не изменять его?

конечно, один подход заключается в объявлении obj как const, но проблема с этим подходом заключается в том, что мы теряем гибкость. что, если мы хотим изменить obj в Bar() после вызова Foo (obj)?

void Bar ()
{

const myType obj; //default constructor

string valuableInfo = Foo(obj); //compiler will complain if Foo signature doesnt match

//we can't modify obj here :(
//...

} //end of Bar()

Очевидным, но плохим решением является следующее:

void Bar ()
{

myType obj; //default constructor

const myType immutableObj {obj}; //copy ctr call
//this is expensive and not recommended if obj is big! want to avoid

string valuableInfo = Foo(immutableObj); //will get the valuable Info risk free
// compiler will complain if Foo has inappropriate signature

//do other stuff
//...

} //end of Bar()

Итак, что является лучшим решением здесь? есть ли способ статически утверждать, что Foo не является инвазивным для объекта, в который мы проходим? можем ли мы временно сделать obj const (без создания нового объекта const) или что-то в этом роде?

4b9b3361

Ответ 1

Foo(static_cast<const myType&>(obj));

Ответ 2

В С++ 17 благодаря P0007R1:

foo(std::as_const(obj));

До С++ 17, если вам нужно часто это делать, писать помощника самостоятельно тривиально:

template<class T> 
constexpr typename std::add_const<T>::type& as_const(T& t) noexcept { return t; }

// prevent accidentally creating an lvalue out of a const rvalue
template<class T> void as_const(const T&&) = delete; 

Конечно, ничто из того, что вы делаете, не может защитить от того, кто сознательно отбрасывает совести. Мерфи, Макиавелли и т.д.

Ответ 3

Вы можете бросить его на месте как Брайан, но вы также можете просто использовать ссылку const:

myType obj;
myType const &cObj = obj;

string valuableInfo = Foo(cObj);

mutate(obj);

Ответ 4

Я собираюсь предложить круговое решение.

конечно, если Foo принимает obj как значение или константу ссылки, мы не будем иметь любые проблемы.

string Foo (const myType & input); //this is fine

string Foo (myType input); // so is this

но мы этого не гарантируем! подпись функции может очень хорошо быть

string Foo (myType & input); //asking for trouble!

Я думаю, здесь есть что-то более неприятное. То, что мы не видим, это документация этой функции Foo: ее комментарии к интерфейсу, значимое имя и т.д.

Первое, что нужно знать об этой функции Foo, прежде чем мы ее используем, - это ее побочные эффекты. Если мы не знаем, что мы будем делать с аргументами, которые мы передаем без гарантии константы (что является лишь слабой гарантией, как указано и становится слабее, чем больше const_casts вы вводите), я бы предположил, что это может указывают на пробой в том, как Foo документирован, перегружен или способ его использования.

Независимо от того, что Foo действительно называется, rotate, display, clamp, lerp, paint, flip, info и т.д., должно быть ясно о его побочные эффекты, и они не должны меняться на логическом уровне между перегрузками. Интерфейсы должны нести даже более твердые гарантии относительно инвариантов, чем именованная константа, о том, что они будут делать и не будут делать.

Например, если у вас есть такой дизайн интерфейса:

/// @return A flipped 's' (no side effects).
Something flip(Something s);

/// Flips 's' (one side effect).
void flip(Something& s);

... это чрезвычайно вызывающий проблемы дизайн: tripwire для всех разработчиков, которые его используют, гнездо/улей ошибки, так как перегрузки различаются по разным причинам с точки зрения их побочных эффектов. Менее запутанный дизайн будет таким:

/// @return A flipped 's' (no side effects).
Something flipped(Something s);

/// Flips 's' (one side effect).
void flip(Something& s);

... тот, который не перегружает flip на основе логических побочных эффектов.

Если вы когда-либо сталкиваетесь с подобным дизайном и вне своего контроля, я бы предложил обернуть его чем-то более разумным, как введение этой функции flipped:

/// @return A flipped 's' (no side effects).
Something flip(Something s);

/// Flips 's' (one side effect).
void flip(Something& s);

/// @return A flipped 's' (no side effects).
Something flipped(Something s)
{
     flip(s);
     return s;
}

... и используя эту функцию flipped вместо этого, где вы четко понимаете ее побочные эффекты и то, что она должна делать на самом деле, и будет продолжать делать независимо от изменчивости аргументов, которые вы проходите. Хотя это более круто, чем введение a const_cast, чтобы вызвать правильную неизменную перегрузку функции, она заставляет источник путаницы в корне, а не работает вокруг очень трипсированного дизайна, заставляя вещи проходить через constness.

constness лучше всего использовать в качестве защитного механизма для потенциальных изменений, которые могут произойти в будущем, а не для того, чтобы обнаружить/обеспечить надлежащее поведение в настоящем. Разумеется, вы могли бы подойти к нему с обоснованием того, что Foo(obj) не будет запускать побочные эффекты в obj в будущем (предположим, что это не так), но на уровне интерфейса не должно быть нестабильность по отношению к побочным эффектам такого рода. Если Foo(obj) не изменяет obj сегодня, то это определенно не должно быть завтра. По крайней мере, интерфейс должен быть устойчивым в этом отношении.

Представьте себе кодовую базу, в которой вызов abs(x) не оставил вас на 100% уверенным, будет ли x изменен или нет, или, по крайней мере, не в будущем. Это не то время, чтобы достигнуть константы для решения этой проблемы: проблема здесь была бы полностью на уровне интерфейса/дизайна по отношению к abs. Не должно быть измененных перегрузок параметров abs, которые создают побочные эффекты. Там никогда не должно быть ничего подобного даже через 10 лет после этого, и это должно быть твердой гарантией, на которую вы можете положиться, не заставляя ваши аргументы abs быть const. Вы должны иметь такую ​​же степень уверенности в отношении любой функции, которую вы используете, даже если она даже удалена.

Итак, хотя могут быть исключения из правила, я бы предложил проверить ваши интерфейсы, убедиться, что они правильно документируют, не перегружены таким образом, что создает разрозненные логические побочные эффекты, основанные на том, какую перегрузку вы используете, и стабильны в отношении того, что они документируют.

Ответ 5

Вы не можете гарантировать что-либо с константой, а также не гарантируете, что параметр не будет изменен нежелательно.

Foo() может легко const_cast<>() удалить const из параметра.

Ответ 6

Вы можете использовать const_cast, чтобы сделать его временно const:

Foo(const_cast<const myType>(obj));

Ответ 7

Значит, слова... Простое объяснение, нет никакой гарантии. Я видел тонны кода, который принимает значение по ссылке const, а не делает const на нем. Он в основном пролиферируется в функциях, которые принимают функтор с состоянием. Поскольку функтор имеет состояние, он берется ссылкой, но поскольку перед ссылками rvalue нельзя было передать временную функцию, принимающую неконстантную ссылку, сигнатура является ссылкой на const. Чем объект const const и состояние изменено.