поменяйте местами неактивные члены данных std :: unique_ptr для объединения

Учитывая союз:

#include <iostream>
#include <memory>
#include <type_traits>
#include <vector>

#include <cassert>
#include <cstdlib>

struct A { int a; };
struct B { int b; };

template< typename X >
struct S
{

std::size_t tag;

std::unique_ptr< X > x;

};

union U
{
S< A > a;
S< B > b;

U(A x) : a{0, std::make_unique< A >(x)} { ; }
U(B x) : b{1, std::make_unique< B >(x)} { ; }

std::size_t tag() { return a.tag; }

~U()
{
switch (tag()) {
case 0 : {
a.~S< A >();
break;
}
case 1 : {
b.~S< B >();
break;
}
default : assert(false);
}
}

void
swap(U & u) noexcept
{
a.x.swap(u.a.x);
std::swap(a.tag, u.a.tag);
}

};

static_assert(std::is_standard_layout< U >{});

int
main()
{
U a{A{ 0}};
U b{B{~0}};
assert((a.tag() == 0) && (a.a.x->a ==  0));
assert((b.tag() == 1) && (b.b.x->b == ~0));
a.swap(b);
assert((a.tag() == 1) && (a.b.x->b == ~0));
assert((b.tag() == 0) && (b.a.x->a ==  0));
return EXIT_SUCCESS;
}

U::tag() Функция верна, так как она позволяет проверять общую начальную подпоследовательность альтернативных элементов данных в Uподобные союзы.

U::swap() работает, но это законно для std::unique_ptrs? Можно ли поменять местами неактивные std::unique_ptrальтернативные данные членов Uподобные союзы?

Это кажется допустимым из-за простой природы std::unique_ptr< X >: это просто обертка X * и для любого A а также B я уверен static_assert((sizeof(A *) == sizeof(B *)) && (alignof(A *) == alignof(B *))); Расположение трюмов и указателей одинаково для всех типов (кроме указателей на члены-данные и функции-члены классов). Это правда?

Пример кода работает нормально. Но очень вероятно, что есть UB, если мы читаем стандарт.

0

Решение

от
§ 9.5 Unions

в частности, примечание о стандартных типах макетов:

… Одна специальная гарантия
сделано для того, чтобы упростить использование объединений: если объединение стандартных макетов содержит несколько стандартных макетов
структуры, которые имеют общую начальную последовательность (9.2), и если объект этого типа объединения стандартного макета
содержит одну из структур стандартного макета, разрешается проверять общую начальную последовательность любого из
члены структуры стандартного макета …

Таким образом, разрешается использовать общую начальную последовательность для любого члена объединения.

В вашем случае общая начальная последовательность определенно std::size_t tag, Тогда нам нужно знать, если std::unique_ptr<T> будет одинаковым для всех T поэтому его также можно рассматривать как часть общей начальной последовательности:

§ 20.8.1 Шаблон класса unique_ptr
[1] Уникальный указатель — это объект, который владеет другим объектом и управляет этим другим объектом через указатель.
Точнее, уникальный указатель — это объект u который хранит указатель на второй объект p

Ага. Но откуда мы знаем, что все указатели будут представлены одинаково? Ну в твоем случае

§ 3.9.2 Типы соединений
[3] … представление значений типов указателей
определяется реализацией. Указатели на cv-квалифицированную и cv-неквалифицированную версии (3.9.3) совместимой с компоновкой
типы должны иметь одинаковые значения представления и требования выравнивания …

Таким образом, мы можем положиться на значение указателя, хранящегося в std::unique_ptr быть представителем в другом члене союза.

Так что нет, здесь нет неопределенного поведения.

1

Другие решения

ИМХО, у вас есть формальное Неопределенное Поведение, потому что вы всегда получаете доступ к части союзов, даже если последняя написанная была б.

Конечно, это работает, потому что кроме его управления, unique_ptr просто содержит необработанный указатель и сохраненный удалитель. Указатели на любой тип имеют одинаковое представление, и, за исключением вопроса выравнивания, безопасно преобразовать указатель на X в указатель на Y и обратно. Так на низком уровне если безопасно поменять местами сырые указатели. Это может быть в большей степени зависеть от реализации, но я предполагаю, что безопасно также менять местами сохраненные средства удаления, потому что то, что на самом деле хранится, обычно является адресом. И вообще, для типов struct A а также struct BДеструкторы просто не работают.

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

Но в вопросе, который я однажды задаю другой возможный случай UB, Ганс Пассант дал ссылка на сайт исследовать работу над продвинутыми компиляторами, способными обнаруживать переполнения буфера. Я действительно думаю, что эту же технику можно использовать для обеспечения соблюдения правил доступа к членам объединения, поэтому такие компиляторы могут вызывать исключения во время выполнения с вашим кодом.

TL / DR: этот код должен работать со всеми известными в настоящее время компиляторами, но, поскольку он не является строго стандартным, он может быть пойман будущими компиляторами. Как таковой я называю это формальное неопределенное поведение.

1

По вопросам рекламы [email protected]