Действительно ли я прав в предположении, что C-стиль бросает (которым препятствуют), только reinterpret_casts? Используя последнего визуально поразительно и легок искать при поиске противных бросков, и следовательно это рекомендуется по броскам C-стиля?
При выбрасывании константы с помощью const_cast и пишущий в первоначально объект константы не определен, какова цель const_cast?
Примечание: Я знаю, что Bjarne справедливо осуждает операции кастинга, что они небезопасны, и даже идет вплоть до утверждения "Ужасной операции, должен иметь ужасную синтаксическую форму". и следовательно многословие кастинга операторов в C++. Таким образом, я попытаюсь минимизировать их использование. Обещание. :)
Нет. Приведение C может выполнять эквивалент const_cast
, static_cast
, reinterpret_cast
или их комбинацию. В случае, если этого было недостаточно, он также может сделать по крайней мере один небольшой трюк, который никакая комбинация новых приведений не может сделать вообще!
Вы можете использовать const_cast
с определенными результатами, если исходная переменная определена без const
, но все, что у вас есть, это указатель const
или ссылка на этот объект . OTOH, если вы считаете, что у вас есть веская причина использовать const_cast
, скорее всего, вам действительно стоит вместо этого поискать mutable
.
Edit: Я полагаю, я должен был сказать это сразу, но приведение в стиле 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
, будет компилироваться, но попытка использовать результат (по крайней мере, один из двух) вызовет серьезную проблему. reinterpret_cast
принимает базовый адрес производного объекта и пытается обрабатывать его так, как если бы это был базовый объект указанного типа - и поскольку (максимум) один базовый объект может фактически существуют по этому адресу, пытаясь относиться к нему так, как другой может / вызовет серьезные проблемы.Изменить: в этом случае классы по существу идентичны, за исключением того, что они печатают, поэтому, хотя что-нибудь могло произойти, с большинством компиляторов оба последних двух распечатают «базу 1». Reinterpret_cast принимает все, что находится по этому адресу, и пытается использовать его как указанный тип. В данном случае я (пытался) сделать что-то безобидное, но видимое. В реальном коде результат, вероятно, будет не таким красивым.
Приведение в стиле C будет работать так же, как static_cast, если бы в коде использовалось публичное наследование вместо частного, то есть он знает, где в производном классе «живет» каждый объект базового класса, и корректирует результат, поэтому каждый Результирующий указатель будет работать, потому что он настроен так, чтобы указывать в нужном месте.
Помните, что приведение констант может воздействовать на что-то другое, кроме исходного идентификатора:
void doit(const std::string &cs)
{
std::string &ms = const_cast<std::string &>(cs);
}
int main()
{
std::string s;
doit(s);
}
Итак, пока doit отбрасывает константу, в этом примере базовая строка не является константой, поэтому неопределенное поведение отсутствует.
Обновление
Хорошо, вот лучший пример того, когда использование 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
.
Нет, приведение типов в стиле C может действовать как reinterpret_cast
s, const-cast
s или 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_cast
используется для удаления const
из типа. Он также может удалить энергозависимый
. Если объект действительно const
, то результат не может быть записан и все равно будет иметь четко определенное поведение. Если, однако, он будет преобразован в const
(путем передачи в функцию const T
, то const_cast
вернет его обратно в не const
в порядке. (Я нашел дополнительную информацию здесь )
reinterpret_cast
не может удалить const
или volatile
из типа.
Отливки в стиле C на самом деле являются кувалдой программирования - вы, по сути, говорите компилятору, что квадратный стержень вон там пройдёт через это круглое отверстие, несмотря ни на что. В этом смысле reinterpret_cast
очень похож.
Основное преимущество, которое я вижу в использовании операторов приведения в стиле C ++, заключается в том, что они позволяют лучше выразить ваши намерения и позволяют компилятору по-прежнему выполнять некоторую проверку операции, которую вы просите выполнить, а не той операции, которую вы просите выполнить. размер подходит всем стиль C.
Что касается const_cast
- вы часто попадаете в ситуацию, когда вы передаете объект через ссылку на константу просто потому, что API требует этого. Скажем, у вас есть функция X, которая задает строку в стиле C:
void X(const char *str) { ... }
Внутри этой функции вы передаете параметр функции C, которая ожидает char *
, даже если она не меняет нить. Единственный способ учесть это - использовать const_cast
str.
Я был бы очень осторожен, используя любой вид приведений, часто это показывает, что что-то не так с вашим дизайном, но иногда вам нужно убедить компилятор, что привязка, на которую он смотрит, не такая квадратная, как предполагается. . Только после этого следует использовать операторы приведения.