Когда статическое приведение безопасно, когда вы используете множественное наследование? - PullRequest
12 голосов
/ 17 октября 2011

Я оказался в ситуации, когда знаю, что это за тип.Тип является одним из трех (или более) уровней наследования.Я называю factory, который возвращает B*, однако T - это либо самый высокий уровень типа (если мой код знает, что это такое), либо 2-й уровень.

В любом случае, я сделал static_cast в шаблоне, которыйэто не то, что нужно делать.Мой вопрос, КОГДА я могу безопасно использовать статическое приведение?Есть ли такое время?Я сделал это в этом случае, потому что я предпочел бы получать ошибки компиляции, когда у меня случайно есть T как что-то дурацкое, которое (произошло и) динамическое приведение игнорирует (и возвращает ноль).Однако, когда я знаю правильный тип, указатель не настроен, поэтому у меня плохой указатель.Я не уверен, почему статическое приведение вообще разрешено в этом случае.

Когда я могу безопасно использовать static_cast для понижающего преобразования?Есть ли когда-нибудь ситуация?Теперь кажется, что всегда неправильно использовать static_cast (когда цель состоит в понижении)

Хорошо, я понял, как воспроизвести его.

#include <iostream>
struct B { virtual void f1(){} };
struct D1 : B {int a;};
struct D2 : B {int a, b; };
struct DD : D1, D2 {};

int main(){
void* cptr = new DD(); //i pass it through a C interface :(
B*  a = (B*)cptr;
D2* b = static_cast<D2*>(a); //incorrect ptr
D2* c = dynamic_cast<D2*>(a); //correct ptr
std::cout << a << " " <<b << " " <<c;
}

Ответы [ 6 ]

10 голосов
/ 17 октября 2011

Кросс-приведение:

struct Base1 { virtual void f1(); };
struct Base2 { virtual void f2(); };
struct Derived : Base1, Base2 {};

Base1* b1 = new Derived();
Base2* b2 = dynamic_cast<Base2*>(b1);

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

См. это объяснение в MSDN

4 голосов
/ 17 октября 2011

Если Derived имеет Base в качестве общедоступного (или иным образом доступного) базового класса, а d имеет тип Derived*, то static_cast<Base*>(d) является повышением .

Это всегда технически безопасно.

И, как правило, не нужно, за исключением случаев, когда у вас есть скрытие (затенение) метода.

Приветствия и hth.,

3 голосов
/ 17 октября 2011

Проблема заключается в этой строке:

B*  a = (B*)cptr;

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

int main(){
  B *bptr = new DD; // convert to common base first (won't compile in this case)
  void* cptr = bptr; // now pass it around as a void pointer
  B*  a = (B*)cptr; // now back to the type it was converted from
  D2* b = static_cast<D2*>(a); // this should be ok now
  D2* c = dynamic_cast<D2*>(a);  // as well as this
  std::cout << a << " " <<b << " " <<c;
}

EDIT: Если вы знаете только, что cptr указывает на некоторый объект, который имеет тип, производный от B во время приведения, то для этого недостаточно информации. Компилятор сообщает, что при попытке преобразовать указатель DD в указатель B.

То, что вам нужно сделать, это что-то вроде этого:

int main(){
  void* cptr = new DD; // convert to void *
  DD* a = (DD*)cptr; // now back to the type it was converted from
  D2* b = static_cast<D2*>(a); // this should be ok now, but the cast is unnecessary
  D2* c = dynamic_cast<D2*>(a);  // as well as this
  std::cout << a << " " <<b << " " <<c;
}

но я не уверен, приемлемо ли это для вашего фактического использования.

2 голосов
/ 17 октября 2011

Вы можете безопасно выполнить upcast, если уверены, что объект на самом деле является экземпляром этого класса.

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

int main()
{
    Base* b = new Derived1;

    Derived1* d1 = static_cast<Derived1*>(b); // OK
    Derived2* d2 = static_cast<Derived2*>(b); // Run-time error - d isn't an instance of Derived2
}
1 голос
/ 10 июня 2015

Для кросс-каста вообще не нужен dynamic_cast ..

   struct Base1 { virtual void f1(); };
   struct Base2 { virtual void f2(); };
   struct Derived : Base1, Base2 {};

   Base1* b1 = new Derived();

   // To cast it to a base2 *, cast it first to a derived *
   Derived *d = static_cast<Derived *>(b1);
   Base2 *b2 = static_cast<Base2 *>(d);
0 голосов
/ 07 декабря 2018

Просто для полноты (зная, что я немного опаздываю, только для опоздавших читателей, таких как я ...):

static_cast может применяться , если используется правильно!

Сначала простой случай:

struct D1 { }; // note: no common base class B!
struct D2 { };
struct DD : D1, D2 { };

Вы можете получить от D1* до D2* через промежуточное понижение до DD*:

D1* d1 = new DD();
D2* d2 = static_cast<DD*>(d1);

Переход к D2 * неявен. Это возможно даже для не виртуального наследования. Но имейте в виду, что вам нужно быть 100% уверенным, что d1 действительно был создан как DD при выполнении отката, в противном случае вы окажетесь в неопределенном поведении!

Теперь более сложный случай: ромбовидный рисунок! Вот что представлено в вопросе:

void* cptr = new DD();
B* a = (B*)cptr;

Теперь этот состав уже опасен! На самом деле здесь реализовано reinterpret_cast:

B* a = reinterpret_cast<B*>(cptr);

То, что вы взамен хотите, - это простой отклик. Как правило, не нужно вообще использовать приведение:

B* a = new DD(); //ambigous!

Исключительно: DD имеет два унаследованных экземпляра B. Это сработало бы, если бы D1 и D2 унаследовали виртуально от B (struct D1/2 : virtual B { }; - не следует путать с B / D1 / D2, являющимся виртуальным классы!).

B* b1 = static_cast<D1*>(new DD());
B* b2 = static_cast<D2*>(new DD());

Приведение к соответствующим базам D1 или D2 теперь проясняет, на какой из двух унаследованных экземпляров B следует указать.

Теперь вы можете вернуть соответствующий другой экземпляр, снова понижая до DD; из-за ромбовидного рисунка вам снова понадобится промежуточный состав:

D2* bb1 = static_cast<DD*>(static_cast<D1*>(b1));
D1* bb2 = static_cast<DD*>(static_cast<D2*>(b2));

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

Вывод: Да, возможно возможно с использованием статических приведений, и это единственный вариант, если участвующие классы не являются виртуальными (примечание: отличаться от виртуального наследования!). Но просто слишком легко ошибиться, иногда даже невозможно (например, если хранить указатели базового типа на произвольные производные типы в std::vector), как правило, решение dynamic_cast Ben является намного более безопасным (при условии, что доступны виртуальные типы данных; если это так, то в приведенном выше примере с вектором это решение only ! ).

...