Вопрос по c++ – C ++: почему const_cast злой?

13

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

В следующем примере:

template <typename T>
void OscillatorToFieldTransformer<T>::setOscillator(const SysOscillatorBase<T> &src)
{
    oscillatorSrc = const_cast<SysOscillatorBase<T>*>(&src);
}

Я использую ссылку, и с помощью const я защищаю свою ссылку от изменения. С другой стороны, если я не использую const_cast, код не будет компилироваться. Почему const_cast здесь плохой?

То же самое относится к следующему примеру:

template <typename T>
void SysSystemBase<T>::addOscillator(const SysOscillatorBase<T> &src)
{
    bool alreadyThere = 0;
    for(unsigned long i = 0; i < oscillators.size(); i++)
    {
        if(&src == oscillators[i])
        {
            alreadyThere = 1;
            break;
        }
    }
    if(!alreadyThere)
    {
        oscillators.push_back(const_cast<SysOscillatorBase<T>*>(&src));
    }
}

Пожалуйста, предоставьте мне несколько примеров, в которых я могу видеть, как плохая идея / непрофессионально использовать const_cast.

Спасибо за любые усилия

Вся цельconst запрещает вам что-то изменять, поэтому ваш код генерирует ошибку. Добавление вconst_cast в основном говорит компилятору замолчать, чтобы вы знали, что делаете. Вот почему это не очень хорошая идея. Если вы не хотите, чтобы это былоconst, не объявляй об этомconst. Cody Gray♦
Видетьэтот вопро Peter Wood
Единственное, что может быть злом, этопрограммис. Применение const не является злом, его просто не следует использовать, когда в этом нет необходимости. Rafał Rawicki
@ user1240436 И после этого уровня профессионализма вы достигаете другого уровня профессионализма, на котором вы понимаете, что вам никогда не нужноconst_cast во всяком случае, и был лучший способ сделать это в первую очередь. Seth Carnegie
Какая Const указывает, что объект, на который ссылается / указывает var, не будет изменен. Если ваша функция требует, чтобы объект был изменен, вы не должны использовать Const квалификатор. const - это подсказки компилятору для оптимизации. tuxuday

Ваш Ответ

9   ответов
32

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

Кроме того, используяconst_cast может вызвать неопределенное поведение. Рассмотрим этот код:

SysOscillatorBase<int> src;
const SysOscillatorBase<int> src2;

...

aFieldTransformer.setOscillator(src);
aFieldTransformer.setOscillator(src2);

При первом звонке все хорошо. Вы можете выброситьconstness объекта, который на самом деле неconst и измените это нормально. Тем не менее, во втором вызове, вsetOscillator вы отбрасываетеconst истинноconst объект. Если вам когда-нибудь понадобится изменить этот объект, вы вызываете неопределенное поведение изменяя объект, который действительно являетсяconst. Поскольку вы не можете сказать, помечен ли объектconst являетсядействительн const где это было объявлено, вы никогда не должны использоватьconst_cast если вы не уверены, что никогда не измените объект. А если нет, какой смысл?

В вашем примере кода вы храните нconst указатель на объект, который может бытьconst, что означает, что вы намереваетесь изменить объект (иначе почему бы просто не сохранить указатель наconst?). Это может привести к неопределенному поведению.

Такж, делая это таким образом, люди могут передавать временные функции вашей функции:

blah.setOscillator(SysOscillatorBase<int>()); // compiles

И тогда вы сохраняете указатель на временный объект, который будет недействительным, когда функция вернет1. У вас нет этой проблемы, если вы берете нconst ссылка

С другой стороны, если я не использую const_cast, код не скомпилируется.

Затем измените код, не добавляйте приведение, чтобы оно работало. Компилятор не компилирует это по причине. Теперь, когда вы знаете причины, вы можете сделать свойvector держи указатели наconst вместо того, чтобы бросать квадратное отверстие в круглое, чтобы оно подходило под ваш колышек.

Так что, повсюду, было бы лучше, чтобы ваш метод принимал не-constместо ссылки @, используяconst_cast почти никогда не бывает хорошей идеей.

1 На самом деле, когда выражение, в котором была вызвана функция, заканчивается.

Я считаю, что, объявив что-то как const, компилятор может сделать определенные предположения w.r.t. многопоточный код. Если вы используете const_cast и нарушаете обещание const, вы можете получить разные значения для вещи в разных потоках. criddell
@ criddell Да, это тоже важная часть. Seth Carnegie
"Вы никогда не должны использовать const_cast, если не уверены, что никогда не будете мутировать объект. А если нет, какой смысл?" Ваш код, возможно, должен передать полученный параметр какой-либо сторонней функции, которую вы не можете изменить, но это не совсем правильно и для которой вы знаете, что она никогда не изменит переданный экземпляр. В этом случае вы можете сделать свой собственный код const корректным и все же иметь возможность передавать экземпляр этой функции, если вы используете const_cast. Kaiserludi
2

const_cast было бы плохо, потому что это позволяет вам расторгнуть договор, указанный методом, т. е. «я не буду изменятьsrc ". Звонящий ожидает, что метод будет придерживаться этого.

10

Ссылки не могут быть изменены, после инициализации они всегда ссылаются на один и тот же объект. Ссылка являетсяconst означает, что объект, на который он ссылается, не может быть изменен. Ноconst_cast отменяет это утверждение и позволяет изменить объект в конце концов.

С другой стороны, если я не использую const_cast, код не скомпилируется.

Это ничего не оправдывает. C ++ отказывается компилировать код, который может позволитьconst объект, который нужно изменить, потому что это означаетconst. Такая программа была бы неверной.const_cast - это средство компиляции неправильных программ - вот в чем проблема.

Например, в вашей программе, похоже, у вас есть объект

std::vector< SysOscillatorBase<T> * > oscillators

Учти это

Oscillator o; // Create this object and obtain ownership
addOscillator( o ); // cannot modify o because argument is const
// ... other code ...
oscillators.back()->setFrequency( 3000 ); // woops, changed someone else's osc.

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

Сила C ++ состоит в том, что он предоставляет инструменты, которые гарантируют владение и семантику стоимости. Когда вы отключаете эти инструменты для компиляции программы, она включает ошибки. Ни один хороший программист не считает это приемлемым.

Как решение этой конкретной проблемы, похоже, чvector (или любой другой используемый вами контейнер) должен хранить объекты по значению, а не по указателю. ЗатемaddOscillator может принимать константную ссылку, но сохраненные объекты могут быть изменены. Кроме того, контейнер владеет объектами и обеспечивает их безопасное удаление без каких-либо действий с вашей стороны.

+ 1 для "не компилируется - не оправдывает ничего". Seth Carnegie
7

const_cast по любой причине, кроме адаптации к Старый) библиотеки, в которых интерфейсы имеют неконстантные указатели / ссылки, но реализации не изменяй аргументынеправильн а также Опасно.

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

Рассмотриvector<double> и предварительно вычисленныйсредни value, * среднее значение обновляется всякий раз, когда через интерфейс класса добавляется новый элемент, так как его потом дешево обновлять, и если его часто запрашивают, нет необходимости каждый раз пересчитывать его из данных. Поскольку вектор является дорогим для копирования, но может потребоваться доступ для чтения, тип может предложить Дешево аксессор, который возвращаетstd::vector<double> const & для кода пользователя, чтобы проверить значения уже в контейнере. Теперь, если пользовательский код отбрасывает константу ссылки и обновляет вектор, инвариант, что класс содержит символсредни не работает, и поведение вашей программы становится неправильным.

Это также опасно, потому что вы не можете гарантировать, что передаваемый вами объект является изменяемым или нет. Рассмотрим простую функцию, которая принимает строку с нулем в конце и преобразует ее в верхний регистр, достаточно просто:

void upper_case( char * p ) {
   while (*p) {
     *p = ::to_upper(*p);
      ++p;
   }
}

Теперь давайте предположим, что вы решили изменить интерфейс, чтобы взятьconst char* и реализация для удаленияconst. Пользовательский код, который работал со старой версией, также будет работать с новой версией, но некоторый код, который будет помечен как ошибка в старой версии, не будет обнаружен во время компиляции. Учтите, что кто-то решил сделать что-то столь же глупое, какupper_case( typeid(int).name() ). Теперь проблема в том, что результатtypeid это не просто постоянная ссылка на модифицируемый объект, а точнее ссылка на постоянный объект. Компилятор может свободно хранитьtype_info объект в сегменте только для чтения и загрузчик для загрузки его в страницу памяти только для чтения. Попытка изменить его приведет к сбою вашей программы.

Обратите внимание, что в обоих случаях вы не можете знать из контекстаconst_cast поддерживаются ли дополнительные инварианты (случай 1) или даже если объект на самом деле является постоянным (случай 2).

С другой стороны, причинаconst_cast чтобы существовать адаптировался к старому коду C, который не поддерживалconst ключевое слово. В течение некоторого времени такие функции, какstrlen взял быchar*, хотя известно и задокументировано, что функция не будет изменять объект. В этом случае безопасно использоватьconst_cast в Адаптироваться тип, чтобы не менять Уст-Несс. Обратите внимание, что C поддерживаетconst уже очень давно иconst_cast имеет меньшее правильное использование.

@ SethCarnegie: лол ... я пропустил++p, спасибо, что указал на это. David Rodríguez - dribeas
Использование этой функции для примера на самом деле хорошо во многих отношениях, чем может показаться на первый взгляд, + 1. Seth Carnegie
Я не указал на это Seth Carnegie
«Любая причина, кроме адаптации к (старым) библиотекам» - я согласен, но попробуйте рассказать об этом людям, которые поверили главе Майерса в «Эффективном C ++» о постоянных и неконстантных методах доступа: - Steve Jessop
2

постоянство созданной переменной
Это может привести к физической константности, поскольку данные помещаются в сегмент только для чтения

постоянство ссылочного параметра / указателя
Это логическая константа, которая поддерживается только компилятором

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

Кроме того, это «запах», что некоторые части вашего кода являются правильными, а другие - нет. Это иногда неизбежно.

В первом примере вы присваиваете константную ссылку тому, что я считаю неконстантным указателем. Это позволит вам изменить исходный объект, для чего требуется как минимум постоянное приведение. Проиллюстрировать

SysOscillatorBase<int> a;
const SysOscillatorBase<int> b;

obj.setOscillator(a); // ok, a --> const a --> casting away the const
obj.setOscilaltor(b); // NOT OK: casting away the const-ness of a const instance

Same относится ко второму примеру.

1

что вы не измените значение. Единственное время, которое вы должны использовать, когда вы используете библиотечную функцию C (где const не существует), как известно, не изменяет значение.

bool compareThatShouldntChangeValue(const int* a, const int* b){
    int * c = const_cast<int*>(a);
    *c = 7;
    return a == b;
}

int main(){
   if(compareThatShouldntChangeValue(1, 7)){
      doSomething();
   }
}
1

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

В приведенных вами примерах кажется, что ваш код не совсем верен. oscillatorSrc, вероятно, должен быть указателем const, хотя если вам действительно нужно изменить значение, вам вообще не следует передавать его как const.

2

Нет, когда ответственно использовал а также когда ты знаешь, что делаешь. (Или вы серьезно копируете код для всех тех методов, которые отличаются только своим модификатором const?)

Однако проблема с const_cast заключается в том, что он может вызывать неопределенное поведение, если вы используете его для переменной, которая изначально был постоянным. То есть если вы объявляете переменную const, то const_cast и пытаетесь ее изменить. И неопределенное поведение не очень хорошая вещь.

В вашем примере содержится именно эта ситуация: возможно, переменная const преобразуется в неконстантную. Чтобы избежать проблемы магазина либоconst SysOscillatorBase<T>* (постоянный указатель) илиSysOscillatorBase<T> (копировать) в списке объектов или передать ссылку вместо константной ссылки.

0

вероятно, должны определить свой контейнер как содержащий константные объекты

template <typename T> struct Foo {
    typedef std::vector<SysOscillator<T> const *>  ossilator_vector;
}

Foo::ossilator_vector<int> oscillators;


// This will compile
SysOscillator<int> const * x = new SysOscillator<int>();
oscillators.push_back(x);

// This will not
SysOscillator<int> * x = new SysOscillator<int>();
oscillators.push_back(x);

Говорят, что если у вас нет контроля над typedef для контейнера, возможно, будет нормально const_cast на интерфейсе между вашим кодом и библиотекой.

Так как это было создано? По крайней мере, так как они использовалиoperator << для вывода текста. Правильное использованиеconst это концепция, которой нужно учить, конечно, но это не так сложно. Это менее запутанно, чем выбор Cint как тип возвращаемого значения функции по умолчанию и его реализацияfor. Mike DeSimone
Если библиотека, которую вы не можете контролировать, плохо написана, и очевидно, что контейнер должен содержать ссылки на const, но const decl был просто пропущен, тогда почему бы не убедиться, чтова code содержит константные ссылки, а затем Адаптироваться используя const_cast на интерфейсе к плохой библиотеке. Когда библиотека исправлена, удалите адаптацию const_cast. Очевидно, что если вынеправильн и передать физический объект const в библиотеку, а затем он пытается изменить его Kaboom Похоже bradgonesurfing
В этом случае, если ты ошибаешься, это не было очевидно. Mike DeSimone
Нет ... Если у вас нет контроля над контейнером, то методы не должны братьconst аргументы. Mike DeSimone
Так как, когда в C ++ было что-то не так, было очевидно? bradgonesurfing

Похожие вопросы