Почему static_cast разрешает downcasts, когда логически он должен отклонять их в целях безопасности, или static_cast не касается безопасности?

#c

#c

Вопрос:

В следующем примере компилятор принимает static_cast downcast, приводящий к неопределенному поведению, в то время как я думал, static_cast что все дело в безопасности (что приведения в стиле C не смогли обеспечить).

 #include <iostream>

class Base {
public:
    int x = 10;
};
class Derived1: public Base 
{
public:
    int y = 20;

};
class Derived2 : public Base 
{
public:
    int z = 30;
    int w = 40;

};

int main() {

    Derived1 d1;

    Base* bp1 = static_cast<Base*>(amp;d1);
    Derived2* dp1 = static_cast<Derived2*>(bp1);

    std::cout << dp1->z << std::endl; // outputs 20
    std::cout << dp1->w << std::endl; // outputs random value
}
  

Комментарии:

1. Я не думаю, что кто-либо когда-либо притворялся, что C — безопасный язык.

2. Нет, для downcasting с безопасностью вам нужны dynamic_cast (и проверки null) — static_cast сообщает компилятору, что вы на 100% уверены, что у вас правильный тип

3. Повышающая трансляция выполняется неявно, специального синтаксиса не требуется. Смысл static_cast в значительной степени заключается в том, чтобы иметь возможность явно выполнять обратные неявные преобразования. Как правило, невозможно проверить во время компиляции, что такое преобразование допустимо — если бы это было так, компилятор разрешил бы это неявно.

4. static_cast является «безопасным» в том смысле, что он не позволяет вам конвертировать между несвязанными типами. Цель безопасности C в значительной степени заключается в том, чтобы быть не таким небезопасным, как C.

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

Ответ №1:

Вы используете dynamic_cast только тогда, когда вы не уверены, что приведение будет успешным, и вы перехватываете исключения или проверяете nullptr. Однако, если вы уверены, что ваша даункастинг будет успешной, язык позволяет вам использовать static_cast (что дешевле). Если вы ошиблись, это ваша проблема. В идеальном мире каждое приведение было бы успешным в 100% случаев. Но мы не живем в идеальном мире. Это немного похоже на индекс массива. arr[5] означает «Я абсолютно уверен, что этот массив содержит по крайней мере 6 элементов. Компилятору не нужно проверять «. Если ваш массив был меньше, чем вы ожидали, это снова ваша проблема.

Ответ №2:

Я думал, static_cast все дело в безопасности (которую приведение в стиле C не смогло обеспечить)

static_cast это безопаснее, чем приведение в стиле C. Но не потому, что с этим невозможно ошибиться. Это безопаснее, потому что это только менее вероятно, что что-то пойдет не так. Когда мы пишем приведение в стиле C, компилятор просматривает этот список, чтобы успокоить нас:

Когда встречается выражение приведения в стиле C, компилятор пытается интерпретировать его как следующие выражения приведения, в этом порядке:

  1. const_cast<new_type>(expression) ;
  2. static_cast<new_type>(expression) с расширениями: указатель или ссылку на производный класс дополнительно разрешается приводить к указателю или ссылке на однозначный базовый класс (и наоборот), даже если базовый класс недоступен (то есть это приведение игнорирует спецификатор частного наследования). То же самое относится к приведению указателя на элемент к указателю на элемент однозначной невиртуальной базы;
  3. static_cast (с расширениями), за которым следует const_cast ;
  4. reinterpret_cast<new_type>(expression) ;
  5. reinterpret_cast за которым следует const_cast .

Выбирается первый вариант, который удовлетворяет требованиям соответствующего оператора приведения, даже если он не может быть скомпилирован (см. Пример). Если приведение может быть интерпретировано более чем одним способом, за которым static_cast следует const_cast , оно не может быть скомпилировано. Кроме того, в нотации приведения в стиле C разрешено приводить от, к и между указателями на неполный тип класса. Если и expression, и new_type являются указателями на неполные типы классов, не указано, будет ли выбрано static_cast или reinterpret_cast .

Смысл в том, чтобы отдавать предпочтение static_cast этому, заключается в том, что у вас есть более точный контроль над результирующим приведением, что обеспечивает дополнительную безопасность. Но это не меняет того факта, что объектная модель C требует, чтобы static_cast поддерживалось приведение, даже когда возможно неопределенное поведение. Только dynamic_cast (кстати, не в приведенном выше списке) имеет дополнительную безопасность для полиморфных типов, но это не без накладных расходов.

Ответ №3:

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

Не хотите его использовать? Не делайте! Вы могли бы переключиться на dynamic_cast (более дорогой) или не приводить.

C позволяет вам делать множество вещей, требующих обдумывания и осторожности. Это один из них.

Но это все еще безопаснее, чем C. static_cast Не позволит вам привести, например, bp1 к UrgleBurgle* .

Конечно, в конечном счете, вы все равно можете использовать приведения в стиле C, если хотите. Я имею в виду, я бы не советовал этого, но вы могли бы. C — это все о выборе (обычно между ужасным вариантом и чуть менее ужасным вариантом).