С++: Почему const_cast зло?

Я продолжаю слышать это выражение, хотя я не могу найти причину, по которой 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.

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

Ответ 1

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

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

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

...

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

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

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

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

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

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

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

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

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


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

Ответ 2

используя const, я защищаю мою ссылку от изменения

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

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

Это не оправдание ни для чего. С++ отказывается компилировать код, который может позволить изменять объект 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 osc.

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

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

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

Ответ 3

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

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

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

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

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 имеет меньшее использование.

Ответ 4

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

Ответ 5

Это по крайней мере проблематично. Вы должны различать две константы:

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

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

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

Кроме того, это "запах", что некоторые части вашего кода являются const-correct, а другие - нет. Иногда это неизбежно.


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

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

То же самое относится к вашему второму примеру.

Ответ 6

в то время как я не могу найти причину, по которой const_cast является злым.

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

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

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

Ответ 7

В основном const promises вы и компилятор, что вы не измените значение. Единственный раз, когда вы будете использовать, когда используете функцию библиотеки 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();
   }
}

Ответ 8

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

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

Ответ 9

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

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 на интерфейсе между вашим кодом и библиотекой.