C ++ Union, два активных члена, отличающиеся только CV-квалификацией - PullRequest
0 голосов
/ 28 сентября 2018

Если у меня есть объединение с двумя членами данных одного типа, отличающееся только CV-квалификацией:

template<typename T>
union A
{
    private:
        T x_priv;
    public:
        const T x_publ;

    public:
        // Accept-all constructor
        template<typename... Args>
        A(Args&&... args) : x_priv(args...) {}

        // Destructor
        ~A() { x_priv.~T(); }
};

И у меня есть функция f, которая объявляет объединение A, таким образом делая x_priv активнымчлен и затем читает x_publ из этого объединения:

int f()
{
    A<int> a {7};

    return a.x_publ;
}

В каждом протестированном мной компиляторе не было ошибок компиляции ни во время выполнения ни для типов int, ни для других, более сложных типов, таких как std :: string и std:: thread.

Я посмотрел на стандарте, было ли это законным поведением, и начал смотреть на разницу T и const T:

6.7.3.1 [basic.type.qualifier]

cv-квалифицированные или cv-неквалифицированные версии типа являются различными типами;однако они должны иметь одинаковые требования к представлению и выравниванию ([basic.align]).

Это означает, что при объявлении const T он имеет точно такое же представление впамять как T.Но потом я обнаружил, что стандарт на самом деле запрещает это для некоторых типов, что я нахожу странным, поскольку не вижу причин для этого.

Я начал поиск доступа к неактивным членам.

Доступ к общей начальной последовательности T и const T разрешен только в том случае, если оба типа стандартных макетов.

10.4.1 [class.union]

В любое время могут быть активны не более одного нестатических членов данных типа объекта объединения [...] [Примечание: одна специальная гарантиясделан для того, чтобы упростить использование объединений: Если объединение стандартного макета содержит несколько структур стандартного макета, которые имеют общую начальную последовательность ([class.mem]), и если нестатический член данных элементаобъект этого типа объединения стандартной компоновки является активным и является одной из структур стандартной компоновки, разрешено проверять общую начальную последовательность любого из элементов структуры стандартной компоновки ;см. [class.mem].- примечание к концу]

Начальная последовательность в основном соответствует порядку нестатических элементов данных с некоторыми исключениями, но поскольку T и const T имеют одинаковые элементы в одной и той же компоновкеэто означает, что общая начальная последовательность T и const T - это все члены T.

10.3.22 [class.mem]

Общая начальная последовательность двух типов структуры стандартного макета ([class.prop]) - это самая длинная последовательность нестатических элементов данных и битовых полей в порядке объявления, начиная с первого такого объекта в каждой из структур, так что соответствующийсущности имеют совместимые с компоновкой типы, либо обе сущности объявлены с атрибутом no_unique_address ([dcl.attr.nouniqueaddr]), либо ни одна из них не существует, и либо обе сущности являются битовыми полями с одинаковой шириной, либо ни одна из них не является битовым полем[Пример:

И вот где ограничения вступают в силу, он ограничивает доступ к некоторым типам, даже если они имеют одинаковое представление в памяти:

10.1.3 [class.prop]

Класс S является классом стандартной компоновки, если он:

  • (3.1) не имеет нестатических членов данных типа нестандартныйКласс макета (или массив таких типов) или ссылка,
  • (3.2) не имеет виртуальных функций и виртуальных базовых классов,
  • (3.3) имеет одинаковое управление доступом для всех нестатическихчлены данных,
  • (3.4) не имеет базовых классов нестандартной компоновки,
  • (3.5) имеет не более одного подобъекта базового класса любого данного типа,
  • (3.6) имеет все нестатические члены-данные и битовые поля в классе, а его базовые классы впервые объявлены в одном и том же классе, а
  • (3.7) не имеет элемента набора M (S) типов, какбазовый класс, где для любого типа X M (X) определяется следующим образом.108 [Примечание: M (X) - это набор типов всех nподобъекты базового класса, которые могут иметь нулевое смещение в X. - примечание конца]
    • (3.7.1) Если Xявляется типом класса без объединения, у которого нет (возможно, унаследованных) нестатических членов-данных, набор M (X) пуст.
    • (3.7.2) Если X является типом класса без объединения сэлемент нестатических данных типа X_0, который имеет нулевой размер или является первым элементом нестатических данных X (где указанный член может быть анонимным объединением), набор M (X) состоит из X_0 и элементов M(X_0).
    • (3.7.3) Если X является типом объединения, набор M (X) является объединением всех M (U_i) и набора, содержащего все U_i, где каждый U_i является типомi-го элемента нестатических данных X.
    • (3.7.4) Если X является типом массива с типом элемента X_e, множество M (X) состоит из X e и элементов M (X_e).).
    • (3.7.5) Если X не является классом, не массивом, набор M (X) пуст.

У меня вопросы , есть ли причина, по которой это не будет корректным поведением? .

По сути, это:

  • Стандартпроизводители забыли объяснить этот конкретный сase?

  • Я не читал какую-то часть стандарта, которая допускает такое поведение?

  • Есть более конкретная причина этого не делатьбыть допустимым поведением?

Причиной правильности синтаксиса является, например, наличие в классе переменной «только для чтения», например:

struct B;

struct A
{
     ... // Everything that struct A had before

     friend B;
}

struct B
{
    A member;

    void f() { member.x_priv = 100; }
}

int main()
{
    B b;
    b.f();                   // Modifies the value of member.x_priv
    //b.member.x_priv = 100; // Invalid, x_priv is private
    int x = b.member.x_publ; // Fine, x_publ is public
}

Таким образом, вам не нужна функция получения, которая может вызвать снижение производительности, и хотя большинство компиляторов оптимизируют это, это все равно увеличивает ваш класс, и для получения переменной вам нужно написать int x = b.get_x().

Также вам не понадобится постоянная ссылка на эту переменную (как описано в в этом вопросе ), которая, хотя она прекрасно работает, добавляет размер вашему классу, что может быть плохо для достаточно больших классов или классов, которыедолжно быть как можно меньше.

И странно было бы писать b.member.x_priv вместо b.x_priv, но это было бы исправимо, если бы у нас были частные члены в анонимных объединениях, тогда мы могли бы переписать это так:

struct B
{
    union
    {
        private:
            int x_priv;
        public:
            int x_publ;

        friend B;
    };

    void f() { x_priv = 100; }
}

int main()
{
    B b;
    b.f();            // Modifies the value of member.x_priv
    //b.x_priv = 100; // Invalid, x_priv is private
    int x = b.x_publ; // Fine, x_publ is public
}

Другим вариантом использования может быть присвоение разных имен одному и тому же элементу данных, например, в Shape, пользователь может ссылаться на позицию как shape.pos, shape.position, shape.cur_pos или shape.shape_pos.

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

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