Я имею в виду следующее. У меня есть несколько классов, которые наследуют один и тот же базовый класс. Союз состоит из указателей этих классов:
#include "stdio.h"
class A {
public:
A() { printf("A\n"); }
virtual ~A() { printf("~A\n"); }
};
class B : public A {
public:
B() { printf("B\n"); }
virtual ~B() { printf("~B\n"); }
};
class C : public A {
public:
C() { printf("C\n"); }
virtual ~C() { printf("~C\n"); }
};
int main() {
union {
B* b;
C* c;
} choice;
choice.b = new B();
delete choice.c; //We have B object, but deleting C
return 0;
}
Кажется, что это работает, но я не уверен, что это не поведение, специфичное для реализации. Могу ли я использовать такой странный метод удаления или я должен запомнить тип сохраненного объекта и удалить его соответственно?
PS Я использую C++ 11 и хочу, чтобы он работал как с GCC, так и с Visual C++ (2012 и выше). В реальном проекте у меня более сложная иерархия классов, но все они являются преемниками (прямо или косвенно) одного и того же абстрактного базового класса
Это двойная доза неопределенного поведения. Во-первых, вы не можете delete
B
через указатель на C
§5.3.5 [expr.delete]/p3:
В первом альтернативе (удалить объект), если статический тип подлежащего удалению объекта отличается от его динамического типа, статический тип должен быть базовым классом динамического типа объекта, подлежащего удалению, и статический тип должен иметь виртуальный деструктор или поведение не определено. Во втором альтернативе (удалить массив), если динамический тип подлежащего удалению объекта отличается от его статического типа, поведение не определено.
Во-вторых, доступ к неактивному члену союза также является неопределенным поведением в C++.
В любом случае нет необходимости использовать союз. B
и C
используют один и тот же базовый класс, поэтому вы можете просто сохранить указатель в A *
.
Вы не должны. Вам разрешено читать только член профсоюза, в который вы в последний раз писались, и вам разрешено удалять объект только с помощью указателя на базовый класс (если он имеет виртуальный деструктор). Возможно, сейчас это работает, но вы можете обнаружить, что он будет беспорядочно разбиваться в будущем, как правило, из-за агрессивного оптимизатора.
Почему бы вам не сохранить указатель на A
вместо объединения?
dynamic_cast
? В вашем текущем решении у вас уже должен быть какой-то механизм для определения динамического типа объекта, чтобы получить доступ к правильному члену объединения. С A*
вы используете тот же механизм, а затем просто static_cast
.
Как было сказано в другом ответе, это неверно C++.
Мое впечатление, что вы хотите сохранить союз указателей, потому что в определенных обстоятельствах вам нужен экземпляр (под) класса B
, а в другом - экземпляр C
, при этом проблема B
и C
имеет не такой же интерфейс, Возможно, вы храните несколько из них в контейнере или просто не знаете до тех пор, пока не будет использован экземпляр экземпляра.
Таким образом, вы можете сохранить свой код таким, какой он есть, возможно, с тегом типа, указывающим, какой экземпляр был создан, а затем использовать switch
каждый раз, когда вам нужно определить правильный код для запуска, или вы могли бы использовать ваши классы для фактического вызова правильную функцию во время выполнения, включив в общий базовый класс B
и C
(1) виртуальный метод и перегружая этот метод в B
и C
с соответствующей ветвью switch
, затем замените объединение простым указателем на базовый класс.
(1), что базовый класс не должен быть A
: если вы не хотите загромождать свое дерево классов, просто создайте другой класс с минимальным интерфейсом, необходимым там, и благодаря C++ множественному наследованию, B
и C
наследует и от него.Не забывайте о виртуальном деструкторе!
Для меня этот случай выглядит законным и нет неопределенного поведения.
12.4.9 Если класс имеет базовый класс с виртуальным деструктором, его деструктор (будь то user- или неявно объявленный) является виртуальным.
b
который перезаписывает членc
, затем вызываетеC::~C
с указателем наB