Преобразование типа C++: преимущество использования явных бросков?

Что такое преимущества использования этих операторов вместо неявного кастинга в C++?

dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
static_cast <new_type> (expression) 

Да ведь где, в которой ситуации мы должны использовать их? И действительно ли это верно, что они редко используются в ООП?

5
задан Lazer 24 June 2010 в 09:17
поделиться

6 ответов

Из приведенного вами списка кастов единственный, который имеет смысл использовать для замены неявного каста, это static_cast.

dynamic_cast используется для понижения суперкласса в его подкласс. Это не может произойти неявно, и на самом деле это не такая уж редкая вещь в ООП. static_cast тоже может быть использован в таком кастинге, однако он более опасен, поскольку не проверяет во время выполнения, что кастинг действителен.

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

7
ответ дан 18 December 2019 в 11:53
поделиться

Просто хочу добавить пример ситуации, в которой используется reinterpret_cast. Представьте, что библиотека дает вам указатель на необработанный сетевой пакет. Чтобы разобраться в структуре данных, вы можете использовать структуры, а затем приводить указатели к этим структурам. Обратите внимание, что у компилятора нет способа проверить, делаете ли вы здесь что-то разумное или просто собираетесь читать в памяти то, что не следует. В реальной ситуации вы бы сначала проверили размер пакета, чтобы убедиться, что он достаточно велик для размещения в нем ваших структур.

В приведенном ниже коде конструктор IPfragment получает пакет и затем преобразовывает указатель во что-то разумное. Я добавил определения ниже.

Если кто-то все еще думает, что использование reinterpret_cast неоправданно, я буду рад услышать о лучшем способе сделать это.

IPfragment::IPfragment( const byte* const pkt_data ) :
    ethernetHeader( reinterpret_cast< const EthernetHeader* >( pkt_data                    )  )
  , ipHeader      ( reinterpret_cast< const IPheader*       >( pkt_data + ETHER_HEADER_LEN )  )
  , payload       ( reinterpret_cast< const byte*           >( ipHeader                    )
                                                             + ( ipHeader->ver_hl & 0x0f ) *4 )
{
}

Вот определения:

typedef uint8_t  byte  ;
typedef uint16_t word  ;
typedef uint32_t dword ;

#define ETHER_ADDR_LEN    6    // Ethernet addresses are 6 bytes
#define ETHER_HEADER_LEN  14   // Ethernet headers are 14 bytes
#define ETHER_TYPE_IP4    8 

struct EthernetHeader
{
   byte   etherDestHost[ETHER_ADDR_LEN];    // Destination host address
   byte   etherSrcHost [ETHER_ADDR_LEN];    // Source host address
   word   etherType;                        // IP? ARP? RARP? etc
};

/* 4 bytes IP address */
struct IPaddress
{
   byte byte1, byte2, byte3, byte4;
};

/* IPv4 header */
struct IPheader
{
   byte ver_hl          ;     // Version (4 bits) + Internet header length (4 bits)
   byte tos             ;     // Type of service
   word tlen            ;     // Total length
   word identification  ;     // Identification
   word flags_fo        ;     // Flags (3 bits) + Fragment offset (13 bits)
   byte ttl             ;     // Time to live
   byte proto           ;     // Protocol
   word crc             ;     // Header checksum
   IPaddress saddr      ;     // Source address
   IPaddress daddr      ;     // Destination address
   dword op_pad         ;     // Option + Padding
};

class IPfragment
{
  public:
    const IPheader*       const ipHeader;
    const EthernetHeader* const ethernetHeader;
    const byte*           const payload;

    IPfragment( const byte* const pkt_data );

    // rest of code omitted for brevity
}
0
ответ дан 18 December 2019 в 11:53
поделиться

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

2
ответ дан 18 December 2019 в 11:53
поделиться

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

Dynamic_cast можно использовать для приведения "вверх по течению" с полиморфизмом. Так что если у вас есть структура вроде этой;

Base -> Derived A

Base -> Derived B

вы можете сделать dynamic_cast(b); (b - указатель на Base, но на самом деле это Derived_B) ;

Если это не класс Derived_B, то вместо преобразованного указателя вы получите 0.

Это намного медленнее, чем static_cast, поскольку проверка выполняется во время выполнения, а не во время компиляции, но предназначение у этого метода другое.

reinterpret_cast просто изменяет метку типа, позволяя использовать забавный FX в стиле C (или "type-punning", как его обычно называют), полезный для протоколов и низкоуровневой работы, но его следует использовать редко.

Обычно большое количество кастов в коде является признаком того, что что-то не так с дизайном вашего кода.

2
ответ дан 18 December 2019 в 11:53
поделиться

Как и любая другая неявная вещь, она может скрывать логику, которую разработчик / рецензент не имел в виду, маскируя ошибки.

3
ответ дан 18 December 2019 в 11:53
поделиться

Одним из преимуществ использования кастов C++ вместо кастов в стиле C является то, что их легко искать. Они также разделяют различные применения кастомов в стиле Си, делая запахи легко идентифицируемыми.

Например, grep для reinterpret_cast может легко найти множество потенциальных проблем и плохих конструкций, в то время как regex, правильно определяющий касты в стиле C, потребует дополнительной проверки для выявления плохих кастов.

Если приведение необходимо, я бы всегда использовал приведение в стиле C++ и никогда в стиле C.

См. стандарты кодирования C++, Саттер и Александреску, пункт 95.

1
ответ дан 18 December 2019 в 11:53
поделиться
Другие вопросы по тегам:

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