Союзы как базовый класс - PullRequest
36 голосов
/ 02 ноября 2010

Стандарт определяет, что Unions нельзя использовать в качестве базового класса, но есть ли для этого конкретные причины?Насколько я понимаю, в Unions могут быть конструкторы, деструкторы, а также переменные-члены и методы для работы с этими переменными.Вкратце, Union может инкапсулировать тип данных и состояние, к которым можно получить доступ через функции-члены.Таким образом, в наиболее распространенных терминах он квалифицируется как класс, и если он может выступать в качестве класса, то почему ему запрещено выступать в качестве базового класса?

Редактировать: Хотя ответы пытаются объяснить причину, я все еще не понимаю, как Союз как производный класс хуже, чем когда Союз как просто классПоэтому, в надежде получить более конкретный ответ и аргументацию, я буду добиваться этого за вознаграждение.Без обид на уже выложенные ответы, спасибо за те!

Ответы [ 7 ]

14 голосов
/ 26 ноября 2010

Тони Парк дал ответ, довольно близкий к истине.Комитет C ++, по сути, не думал, что стоит сделать усилия, чтобы сделать союзы сильной частью C ++, подобно тому, как массивы рассматривались как устаревшие вещи, которые мы должны были унаследовать от C, но на самом деле не хотели.

У союзов есть проблемы: если мы разрешаем не-POD типы в союзах, как они создаются?Это, безусловно, можно сделать, но не обязательно безопасно, и любое рассмотрение потребует ресурсов комитета.И конечный результат будет менее чем удовлетворительным, потому что на нормальном языке действительно требуются дискриминируемые союзы, и голые союзы C никогда не могут быть возведены в дискриминационные союзы способом, совместимым с C (что я могу себе представить, во всяком случае).

Чтобы уточнить технические вопросы: поскольку вы можете обернуть объединение только POD-компонентом в структуру, не теряя ничего, нет никакого преимущества в том, чтобы объединять в качестве баз.С компонентами объединения POD-only нет проблем ни с явными конструкторами, просто назначающими один из компонентов, ни с использованием битблита (memcpy) для конструктора (или назначения), генерируемого компилятором.

Однако такие объединения не нужныне достаточно полезен, чтобы беспокоиться, за исключением того, чтобы сохранить их, поэтому существующий код C может считаться допустимым C ++.Эти объединения только для POD нарушены в C ++, потому что они не могут сохранить жизненно важный инвариант, которым они обладают в C: любой тип данных может использоваться как тип компонента.

Чтобы сделать объединения полезными, мы должны разрешить конструируемые типы в качестве членов.Это важно, поскольку недопустимо просто назначать компонент в теле конструктора, либо самого объединения, либо любой включающей структуры: например, нельзя присвоить строку неинициализированному строковому компоненту.

Отсюда следует придумать некоторые правила для инициализации компонента объединения с помощью mem-initialisers, например:

union X { string a; string b; X(string q) : a(q) {} };

Но теперь возникает вопрос: что это за правило?Обычно правило состоит в том, что вы должны инициализировать каждый член и базу класса, если вы не сделаете этого явно, конструктор по умолчанию используется для остатка, и если один тип, который не был явно инициализирован, не имеет конструктора по умолчанию, этоошибка [Исключение: конструкторы копирования, по умолчанию используется конструктор копирования члена].

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

Так что теперь возникает вопрос: как бы вынаписать, скажем, конструктор копирования?Конечно, это вполне возможно сделать и получить правильно, если вы спроектируете свой союз, как, скажем, спроектированы союзы событий X-Windows: с тегом дискриминанта в каждом компоненте, но вам придется использовать оператор размещения new, чтобы сделать это.и вам придется нарушить написанное выше правило, которое на первый взгляд оказалось правильным!

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

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

Хорошо, есть ли альтернативы?Возможно, да, но их не так легко придумать, и труднее убедить более 100 человек, что стоит подумать на трехдневной встрече, наполненной другими вопросами.

Жаль, что комитет не выполнил вышеизложенное правило: профсоюзы являются обязательными для выравнивания произвольных данных, а внешнее управление компонентами не так уж сложно сделать вручную, а тривиально и совершенно безопасно, когда код генерируется подходящим алгоритмом, другими словами, правило обязательно , если вы хотите использовать C ++ в качестве целевого языка компилятора и по-прежнему генерировать читаемый, переносимый код. Такие объединения с конструируемыми членами имеют много применений, но наиболее важным является представление стекового фрейма функции, содержащей вложенные блоки: каждый блок имеет локальные данные в структуре, и каждая структура является компонентом объединения, нет необходимости в каких-либо конструкторах или такой, компилятор будет просто использовать размещение нового. Объединение обеспечивает выравнивание и размер, а также свободный доступ к компонентам. [И нет другого подходящего способа получить правильное выравнивание!]

Поэтому ответ на ваш вопрос таков: вы задаете не тот вопрос. Нет никакого преимущества в том, что союзы, использующие только POD, являются базовыми, и они, конечно, не могут быть производными классами, потому что тогда они не будут POD. Чтобы сделать их полезными, требуется некоторое время, чтобы понять, почему нужно следовать принципу, используемому повсеместно в C ++: пропущенные биты не являются ошибкой, если вы не пытаетесь их использовать.

9 голосов
/ 02 ноября 2010

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

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

Во избежание этого тонкого, но опасного противоречия, которое фактически подрывает систему типов, происходящую из объединения, не допускается.

7 голосов
/ 02 ноября 2010

Бьярн Страуструп сказал, что в «Аннотированном C ++ справочном руководстве» для этого нет особых причин.

2 голосов
/ 25 ноября 2010

Я думаю, что вы сами получили ответ в своих комментариях к ответу EJP.

Я думаю, что союзы вообще включены только в C ++, чтобы быть обратно совместимыми с C. Я думаю, союзы казались хорошей идеейв 1970 году в системах с крошечным пространством памяти.К тому времени, когда появился C ++, я думаю, что союзы уже выглядели менее полезными.

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

1 голос
/ 23 ноября 2010

Заголовок спрашивает, почему союзы не могут быть базовым классом, но вопрос, как представляется, касается союзов как производного класса.Итак, что это?

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

struct V3 {
    union {
        struct {
            float x,y,z;
        };
        float f[3];
    };
};

Обоснование объединения как производного класса, вероятно, проще: результат не будет объединением.Союзы должны быть объединением всех их членов и всех их баз.Это достаточно справедливо и может открыть некоторые интересные возможности шаблона, но у вас будет ряд ограничений (все базы и члены должны быть POD - и вы сможете наследовать дважды, потому что производный тип по своей природе не является-POD?), Этот тип наследования будет отличаться от другого типа языкового вида спорта (хорошо, не то, что это остановило C ++ раньше), и он в любом случае избыточен - существующая функциональность объединения будет работать так же хорошо.

Страуструп говорит об этом в книге D & E:

Как и в случае void *, программисты должны знать, что профсоюзы по своей природе опасны, их следует избегать, где это возможно, и обращаться с ними следует с помощьюособая осторожность, когда это действительно необходимо.

(Elision не меняет смысла.)

Так что я представляю, что решение является произвольным, и он просто не видел причин менять профсоюзфункциональность (она работает как есть с подмножеством C в C ++), и поэтому не планировала никакой интеграции с новым C ++ feAtures.И когда ветер изменился, он застрял таким образом.

0 голосов
/ 13 февраля 2015

Вы можете наследовать макет данных объединения, используя функцию анонимного объединения из C ++ 11.

#include <cstddef>

template <size_t N,typename T>
struct VecData {
  union {
    struct {
      float x;
      float y;
      float z;
    };
    float a[N];
  };
};

template <size_t N, typename T>
  class Vec : public VecData<N,T> {
    //methods..
};

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

0 голосов
/ 23 ноября 2010

Вот мое предположение для C ++ 03.

Согласно $ 9,5 / 1, в C ++ 03 Unions не могут иметь виртуальные функции. Весь смысл осмысленного деривации в том, чтобы иметь возможность переопределять поведение в производном классе. Если объединение не может иметь виртуальных функций, это означает, что нет смысла выводить из объединения.

Отсюда и правило.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...