Нужны разъяснения в стиле C, переинтерпретации и константных приведениях - PullRequest
6 голосов
/ 27 февраля 2010

Прав ли я, предполагая, что приведение в стиле C (которое не поощряется) - не что иное, как reinterpret_casts? Использование последнего визуально поразительно и легко найти при поиске отвратительных бросков, и, следовательно, рекомендуется по сравнению с бросками в стиле C?

Если отбрасывание const с использованием const_cast и запись в исходный объект const не определены, какова цель const_cast?

Примечание: Я знаю, что Бьярне справедливо осуждает операции приведения, что они небезопасны, и даже доходит до утверждения, что «операция безобразный должна иметь уродливую синтаксическую форму". и, следовательно, многословие операторов приведения в C ++. Поэтому я постараюсь свести к минимуму их использование. Promise. :)

Ответы [ 5 ]

5 голосов
/ 27 февраля 2010

Нет. C-бросок может делать эквивалент 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, если бы код использовал публичное наследование вместо private - т.е. он знает, где в производном классе «живет» каждый объект базового класса, и корректирует результат, поэтому каждый результирующий указатель будет работать, потому что он был настроен так, чтобы указывать в нужном месте.

1 голос
/ 27 февраля 2010

Помните, что приведение констант может действовать не на исходный идентификатор, а на что-то другое:

void doit(const std::string &cs)
{
    std::string &ms = const_cast<std::string &>(cs);
}

int main()
{
    std::string s;
    doit(s);
}

Так что, пока doit отбрасывает 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.

1 голос
/ 27 февраля 2010

Нет, приведения в стиле C могут действовать как reinterpret_cast с, const-cast с или static_cast с в зависимости от ситуации. Вот почему они не одобряются - вы видите приведение кода в стиле 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
0 голосов
/ 27 февраля 2010

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

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

Относительно const_cast - вы часто попадаете в ситуацию, когда вы передаете объект по константной ссылке просто потому, что API требует от вас этого. Скажем, у вас есть функция X, которая задает строку в стиле C:

void X(const char *str) { ... }

Внутри этой функции вы передаете параметр функции C, которая ожидает char *, даже если она не меняет строку. Единственный способ приспособиться к этому - const_cast ул.

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

0 голосов
/ 27 февраля 2010

const_cast используется для удаления const из типа. Он также может удалить volatile. Если объект действительно const, то результат не может быть записан и все еще иметь четко определенное поведение. Если, однако, он повышен до const (путем передачи в функцию const T, тогда const_cast возвращение его к non- const в порядке. (Я нашел дополнительную информацию здесь )

reinterpret_cast не может удалить const или volatile из типа.

см. Также

...