C ++ не может преобразовать из базы A в производный тип B через виртуальную базу A - PullRequest
51 голосов
/ 19 сентября 2010

У меня есть три класса:

class A {};

class B : virtual public A {};
class C : virtual public A {};

class D: public B, public C {};

При попытке статического приведения от A * к B * я получаю следующую ошибку:

cannot convert from base A to derived type B via virtual base A

Ответы [ 7 ]

87 голосов
/ 20 сентября 2010

Чтобы понять систему приведения, вам необходимо погрузиться в объектную модель.

Классическим представлением модели простой иерархии является ограничение: что если B происходит от A, тогда объект B будет фактически содержать подобъект A наряду со своими собственными атрибутами.

В этой модели downcasting - это простое манипулирование указателем с помощью смещения, известного во время компиляции, которое зависит от макета памяти B.

Это то, что static_cast делает: статическое приведение называется статическим, поскольку вычисление того, что необходимо для преобразования, выполняется во время компиляции, будь то указатель арифметики или преобразования (*).

Однако, когда virtual наследование пинает вещи, как правило, становится немного сложнее. Основная проблема заключается в том, что при наследовании virtual все подклассы совместно используют один и тот же экземпляр подобъекта. Для этого B будет иметь указатель на A вместо A, а объект базового класса A будет создан вне B.

Следовательно, во время компиляции невозможно вывести необходимую арифметику указателя: она зависит от типа объекта во время выполнения.

Всякий раз, когда есть зависимость типа времени выполнения, вам нужен RTTI (Информация о типе RunTime), и использование RTTI для приведений является задачей dynamic_cast .

В итоге:

  • downcast во время компиляции: static_cast
  • время выполнения downcast: dynamic_cast

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

(*) Как отметил @curiousguy в комментариях, это относится только к снижению рейтинга. A static_cast позволяет транслировать вне зависимости от виртуального или простого наследования, хотя в этом случае приведение также не требуется.

12 голосов
/ 19 сентября 2010

Насколько я знаю, вам нужно использовать dynamic_cast, потому что наследование virtual, а вы уныние.

6 голосов
/ 19 сентября 2010

Вы не можете использовать static_cast в этой ситуации, потому что компилятор не знает смещения B относительно A во время компиляции.Смещение должно быть рассчитано во время выполнения на основе точного типа наиболее производного объекта.Поэтому вы должны использовать dynamic_cast.

4 голосов
/ 20 сентября 2010

Согласно стандартным документам,

Сечение 5.2.9 - 9 , для Статическое приведение ,

Значение типа «указатель на cv1 B», где B - тип класса, может быть преобразовано в значение типа «указатель на cv2» D », где D - класс, производный (пункт 10) от B, если допустимое стандартное преобразование из« указателя на D »в« указатель на B » существует (4.10), cv2 - это та же квалификация cv, что и cv1, или более высокая квалификация, чем cv1, а B не является ни виртуальным базовым классом D или базовый класс виртуального базового класса D.

Следовательно, это невозможно, и вы должны использовать dynamic_cast ...

4 голосов
/ 19 сентября 2010

Да, вам нужно использовать dynamic_cast, но вы должны сделать базовый класс A полиморфным, например, добавив виртуальный дтор.

1 голос
/ 16 ноября 2012

Я не знаю, является ли это "безопасным", но.

Предполагая,

B получено из A (и чисто виртуального)

Так как я ЗНАЮ, чтоуказатель на B все еще остается указателем на B.

    class A
    {
            virtual void doSomething(const void* p) const =0;
    };

    class B
    {
    public:
            int value;
            virtual void doSomething(const void*p)const
            {
            const B * other = reinterpret_cast<const B*>(p);
            cout<<"hello!"<< other->value <<endl;
            }
    };

    int main()
    {
            B  foo(1),bar(2);
            A * p = &foo, q=&bar;
            p->doSomething(q);
            return 0;
    }

эта программа выполняется и корректно возвращает печать "привет!"и значение другого объекта (в данном случае «2»).

кстати, то, что я делаю, очень небезопасно (лично я даю разные ID каждому классу и утверждаю после переинтерпретации приведенияэтот текущий идентификатор равен другому идентификатору, чтобы быть уверенным, что мы что-то делаем с двумя равными классами), и, как вы видите, я ограничился «постоянными» методами.Таким образом, это будет работать с «неконстантными» методами, но если вы сделаете что-то неправильно, обнаружение ошибки будет почти невозможно.И даже с утверждением есть 1 шанс из 4 миллиардов для успешного утверждения, даже если оно должно быть неудачным (assert (ID == other-> ID);)

Кстати ... Хороший ОО-дизайнне должно требовать такого рода вещей, но в моем случае я попытался реорганизовать / изменить дизайн кода без возможности переопределения приведения.Вообще говоря, вы МОЖЕТЕ избежать подобных вещей.

1 голос
/ 20 сентября 2010

$ 5.2.9 / 2- "Выражение e может быть явно преобразован в тип T с помощью static_cast формы static_cast (e) если объявление «Т т (е);» является правильно сформированным, для некоторых изобрел временную переменную t (8.5). "

В вашем коде вы пытаетесь использовать static_cast с 'T = B *' и 'e = A *'

Теперь «B * t (A *)» не является правильно сформированным в C ++ (но «A * t (B *)» объясняется тем, что «A» является виртуальной однозначной и доступной базой «B». Поэтому код дает ошибку.

...