Нужны пояснения в стиле C, переосмысление и приведение const
-
23-09-2019 - |
Вопрос
Прав ли я, предполагая, что приведения в стиле C (которые не поощряются) - это не что иное, как reinterpret_casts?Использование последнего визуально бросается в глаза и упрощает поиск при поиске нежелательных приведений, и, следовательно, его рекомендуется использовать по сравнению с приведениями в стиле C.?
Если отбрасывание const с помощью const_cast и запись в изначально константный объект не определены, какова цель const_cast?
Примечание: Я знаю, что Бьярне справедливо осуждает кастинговые операции за то, что они небезопасны, и даже доходит до того, что заявляет: " уродливый операция должна иметь уродливую синтаксическую форму". и, следовательно, многословие операторов приведения в C ++.Поэтому я постараюсь свести к минимуму их использование.Обещаю.:)
Решение
Нет.Приведение C может выполнять эквивалент a const_cast
, а static_cast
, а reinterpret_cast
, или их комбинация.В случае, если этого было недостаточно, он также может выполнить по крайней мере один незначительный трюк, который НЕТ комбинация новых слепков может сделать все!
Вы можете использовать const_cast
с определенными результатами, если исходная переменная определена без const
, но все , что у вас есть , - это const
указатель или ссылка на этот объект.OTOH, если вы считаете, что у вас есть веская причина использовать const_cast
, скорее всего , вам действительно стоит посмотреть вверх mutable
вместо этого.
Редактировать:Полагаю, мне следовало сказать это сразу, но приведение в стиле C может быть преобразовано в недоступный базовый класс.Например, рассмотрим что-то вроде:
[Править:Я обновляю код до чего-то, что будет компилироваться и (обычно) демонстрировать проблему.]
#include <iostream>
class base1 {
public:
virtual void print() { std::cout << "base 1\n"; }
};
class base2 {
public:
virtual void print() { std::cout << "base 2\n"; }
};
class derived : base1, base2 {}; // note: private inheritance
int main() {
derived *d = new derived;
base1 *b1 = (base1 *)d; // allowed
b1->print(); // prints "base 1"
base2 *b2 = (base2 *)d; // also allowed
b2->print(); // prints "base 2"
// base1 *bb1 = static_cast<base *>(d); // not allowed: base is inaccessible
// Using `reinterpret_cast` allows the code to compile.
// Unfortunately the result is different, and normally won't work.
base1 *bb2 = reinterpret_cast<base1 *>(d);
bb2->print(); // may cause nasal demons.
base2 *bb3 = reinterpret_cast<base2 *>(d);
bb3->print(); // likewise
return 0;
}
Код, использующий reinterpret_cast
s будет скомпилирован - но попытка использовать результат (по крайней мере, одного из двух) вызовет серьезную проблему.В reinterpret_cast
принимает База адрес производного объекта и пытается обработать его так, как если бы это был указанный тип базового объекта - и поскольку (самое большее) один базовый объект может фактически существовать по этому адресу, попытка обработать его как другой может / вызовет серьезные проблемы.Редактировать:В этом случае классы по существу идентичны, за исключением того, что они печатают, поэтому, хотя что-либо мог бы случается, что в большинстве компиляторов оба последних двух выводят "base 1".reinterpret_cast принимает все, что оказывается по этому адресу, и пытается использовать его в качестве указанного типа.В данном случае я (попытался) заставить это сделать что-то безобидное, но видимое.В реальном коде результат, вероятно, будет не таким красивым.
Приведение в стиле C будет работать так же, как static_cast, если бы код использовал публичное наследование вместо частного, т. Е.он знает, где в производном классе "живет" каждый объект базового класса, и корректирует результат, поэтому каждый результирующий указатель будет работать, потому что он был настроен так, чтобы указывать на нужное место.
Другие советы
Нет, приведения в стиле C могут действовать как reinterpret_cast
s, const-cast
ы или static_cast
s в зависимости от ситуации.Вот почему они обескуражены - вы видите приведение в коде в стиле C и должны искать детали, чтобы увидеть, что оно будет делать.Например:
const char* source;
int* target = (int*)source;// - acts as const_cast and reinterpret_cast at once
//int* target = retinterpret_cast<int*>source;// - won't compile - can't remove const
Помните, что приведение const может действовать на что-то отличное от исходного идентификатора:
void doit(const std::string &cs)
{
std::string &ms = const_cast<std::string &>(cs);
}
int main()
{
std::string s;
doit(s);
}
Таким образом, хотя doit отбрасывает const , в этом примере базовая строка не является const, поэтому неопределенного поведения нет.
Обновить
Хорошо, вот лучший пример того, когда использование const_cast не является полностью бесполезным.Мы начинаем с базового класса с виртуальной функцией, которая принимает параметр const:
class base
{
public:
virtual void doit(const std::string &str);
};
и теперь вы хотите переопределить эту виртуальную функцию.
class child : public base
{
public:
virtual void doit(const std::string &str)
{
std::string &mstr = const_cast<std::string &>(str);
}
};
Из-за логики / структуры вашего кода вы знаете, что child::doit
будет вызываться только с неконстантными строками (и class base
находится не под вашим контролем, поэтому вы не можете изменить его, равно как и подпись child::doit
потому что тогда это больше не будет переопределяться base::doit
).В этом случае безопасно отбросить const.
Да, это рискованно.Возможно, когда вы пишете это, это правда, что исполнение никогда не достигнет child::doit
с неконстантной строкой, и код является допустимым.Но это может измениться либо при сохранении вашей программы, либо, возможно, когда вы перестроите и установите последнюю версию class base
.
const_cast
используется для удаления const
от типа.Он также может удалить volatile
.Если объект действительно является const
тогда результат не может быть записан и при этом оставаться четко определенным поведением.Если, однако, он будет повышен до const
(будучи переданным в const T
функции, то const_cast
возвращая его к не-const
все в порядке.( я нашел еще кое-какую информацию здесь)
reinterpret_cast
не может удалить const
или volatile
от типа.
Приведения в стиле C на самом деле являются кувалдой программирования - вы, по сути, сообщаете компилятору, что квадратный стержень вон там пролезет в это круглое отверстие, несмотря ни на что.В этом смысле, reinterpret_cast
очень похоже.
Главное преимущество, которое я вижу в использовании операторов приведения в стиле C ++, заключается в том, что они позволяют вам лучше выразить свое намерение и позволяют компилятору по-прежнему выполнять некоторую проверку операции, которую вы просите его выполнить, а не использовать универсальное приведение в стиле C.
Относительно const_cast
- вы часто попадаете в ситуацию, когда вы передаете объект по ссылке const просто потому, что API требует, чтобы вы это делали.Допустим, у вас есть функция X, которая обрабатывает строку в стиле C:
void X(const char *str) { ... }
Внутри этой функции вы передаете параметр функции C, которая ожидает char *
, даже если это не меняет строку.Единственным способом приспособиться к этому было бы const_cast
ул.
Я был бы очень осторожен, используя любой вид приведения, часто это показывает, что с вашим дизайном что-то не совсем так, но иногда вам приходится убеждать компилятор, что привязка, на которую он смотрит, не такая квадратная, как он предполагает.Только после этого вы должны использовать операторы приведения.