Могу ли я удалить компонент объединения, не проверяя, хранит ли он этот объект?

0

Я имею в виду следующее. У меня есть несколько классов, которые наследуют один и тот же базовый класс. Союз состоит из указателей этих классов:

#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 и выше). В реальном проекте у меня более сложная иерархия классов, но все они являются преемниками (прямо или косвенно) одного и того же абстрактного базового класса

  • 6
    Очевидное неопределенное поведение очевидно.
  • 0
    Члены союза занимают одни и те же байты. Вы назначаете член b который перезаписывает член c , затем вызываете C::~C с указателем на B
Показать ещё 7 комментариев
Теги:
c++11

4 ответа

5
Лучший ответ

Это двойная доза неопределенного поведения. Во-первых, вы не можете delete B через указатель на C §5.3.5 [expr.delete]/p3:

В первом альтернативе (удалить объект), если статический тип подлежащего удалению объекта отличается от его динамического типа, статический тип должен быть базовым классом динамического типа объекта, подлежащего удалению, и статический тип должен иметь виртуальный деструктор или поведение не определено. Во втором альтернативе (удалить массив), если динамический тип подлежащего удалению объекта отличается от его статического типа, поведение не определено.

Во-вторых, доступ к неактивному члену союза также является неопределенным поведением в C++.

В любом случае нет необходимости использовать союз. B и C используют один и тот же базовый класс, поэтому вы можете просто сохранить указатель в A *.

2

Вы не должны. Вам разрешено читать только член профсоюза, в который вы в последний раз писались, и вам разрешено удалять объект только с помощью указателя на базовый класс (если он имеет виртуальный деструктор). Возможно, сейчас это работает, но вы можете обнаружить, что он будет беспорядочно разбиваться в будущем, как правило, из-за агрессивного оптимизатора.

Почему бы вам не сохранить указатель на A вместо объединения?

  • 0
    B и C содержат дополнительные методы и поля. Поэтому я должен использовать объединение с разными указателями из-за более высокой производительности. Я получаю доступ к этому объединению через специальные методы, которые проверяют наличие и возвращает nullptr или действительный указатель типа потребности. Я думаю, что использование dynamic_cast снизит производительность
  • 0
    @ user2717575, что ты имеешь в виду dynamic_cast ? В вашем текущем решении у вас уже должен быть какой-то механизм для определения динамического типа объекта, чтобы получить доступ к правильному члену объединения. С A* вы используете тот же механизм, а затем просто static_cast .
Показать ещё 2 комментария
1

Как было сказано в другом ответе, это неверно C++.

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

Таким образом, вы можете сохранить свой код таким, какой он есть, возможно, с тегом типа, указывающим, какой экземпляр был создан, а затем использовать switch каждый раз, когда вам нужно определить правильный код для запуска, или вы могли бы использовать ваши классы для фактического вызова правильную функцию во время выполнения, включив в общий базовый класс B и C (1) виртуальный метод и перегружая этот метод в B и C с соответствующей ветвью switch, затем замените объединение простым указателем на базовый класс.


(1), что базовый класс не должен быть A: если вы не хотите загромождать свое дерево классов, просто создайте другой класс с минимальным интерфейсом, необходимым там, и благодаря C++ множественному наследованию, B и C наследует и от него.Не забывайте о виртуальном деструкторе!

  • 0
    Да, я храню информацию о том, какой компонент выбора используется. Просто хотел устранить выключатель в процедуре уничтожения.
  • 0
    Посмотрите сноску, которую я добавил, это может помочь, если вы не знакомы с приведениями.
0

Для меня этот случай выглядит законным и нет неопределенного поведения.

  1. Он использует type-punning, и это законно, потому что B * b и C * c в членах объединения являются указателями и могут быть преобразованы в char-массив.
  2. Оба B и C имеют виртуальный деструктор из-за базового класса (не потому, что база одна и та же !, а потому, что база имеет виртуальный деструктор).

    12.4.9 Если класс имеет базовый класс с виртуальным деструктором, его деструктор (будь то user- или неявно объявленный) является виртуальным.

  3. В то время как вызов деструктора (поскольку он является виртуальным), точный адрес функции будет выбран из переменной выбора, и будет вызвана правильная последовательность деструктора. Таким образом, никакого ЛЮБОГО неопределенного поведения вообще нет.
  • 0
    «Он использует наказание по типу» - что не разрешено в C ++.
  • 0
    9.5 Примечание. Для упрощения использования объединений делается одна специальная гарантия: если объединение стандартного макета содержит несколько структур стандартного макета, имеющих общую начальную последовательность (9.2), и если объект этого типа объединения стандартного макета содержит одну из структур стандартного макета, разрешено проверять общую начальную последовательность любого из элементов структуры стандартного макета; см. 9.2. —Конечная записка
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню