Свойства с нулевой стоимостью с синтаксисом элемента данных
Я (заново?) Изобрел этот подход к свойствам с нулевой стоимостью с помощью синтаксиса члена данных. Под этим я подразумеваю, что пользователь может написать:
some_struct.some_member = var;
var = some_struct.some_member;
и эти обращения к членам перенаправляют на функции-члены с нулевыми издержками.
Хотя первоначальные тесты показывают, что этот подход работает на практике, я далеко не уверен, что он свободен от неопределенного поведения. Вот упрощенный код, который иллюстрирует подход:
template <class Owner, class Type, Type& (Owner::*accessor)()>
struct property {
operator Type&() {
Owner* optr = reinterpret_cast<Owner*>(this);
return (optr->*accessor)();
}
Type& operator= (const Type& t) {
Owner* optr = reinterpret_cast<Owner*>(this);
return (optr->*accessor)() = t;
}
};
union Point
{
int& get_x() { return xy[0]; }
int& get_y() { return xy[1]; }
std::array<int, 2> xy;
property<Point, int, &Point::get_x> x;
property<Point, int, &Point::get_y> y;
};
Тестовый драйвер демонстрирует, что подход работает, и он действительно нулевой (свойства не занимают дополнительной памяти):
int main()
{
Point m;
m.x = 42;
m.y = -1;
std::cout << m.xy[0] << " " << m.xy[1] << "\n";
std::cout << sizeof(m) << " " << sizeof(m.x) << "\n";
}
Реальный код немного сложнее, но суть подхода здесь. Он основан на использовании объединения реальных данных (в данном примере xy
) и пустых объектов свойств. (Реальные данные должны быть стандартным классом макета, чтобы это работало).
Объединение необходимо, потому что в противном случае свойства без необходимости занимают память, несмотря на то, что они пусты.
Почему я думаю, что здесь нет UB? Стандарт разрешает доступ к общей начальной последовательности членов объединения стандартных макетов. Здесь общая начальная последовательность пуста. К элементам данных x
и y
вообще нет доступа, так как нет элементов данных. Мое чтение стандарта указывает на то, что это разрешено. reinterpret_cast
должен быть в порядке, потому что мы приводим член объединения к содержащему его объединению, и они являются взаимозаменяемыми по указателю.
Это действительно разрешено стандартом, или я пропускаю некоторые UB здесь?
Ответы
Ответ 1
TL; DR Это UB.
[basic.life]
Аналогично, до того, как началось время жизни объекта, но после того, как было выделено хранилище, которое будет занимать объект, или после того, как закончился срок жизни объекта, и до того, как хранилище, которое занимал объект, будет повторно использовано или освобождено, любое значение glvalue, которое относится к Оригинальный объект может быть использован, но только ограниченным образом. О строящемся или разрушаемом объекте см. [Class.cdtor]. В противном случае такое glvalue относится к выделенному хранилищу, и использование свойств glvalue, которые не зависят от его значения, является четко определенным. Программа имеет неопределенное поведение, если: [...]
- glvalue используется для вызова нестатической функции-члена объекта, или
По определению, неактивный член союза не существует
Возможный обходной путь - использовать С++ 20 [[no_unique_address]]
struct Point
{
int& get_x() { return xy[0]; }
int& get_y() { return xy[1]; }
[[no_unique_address]] property<Point, int, &Point::get_x> x;
[[no_unique_address]] property<Point, int, &Point::get_y> y;
std::array<int, 2> xy;
};
static_assert(offsetof(Point, x) == 0 && offsetof(Point, y) == 0);
Ответ 2
Вот что говорит правило об общей начальной последовательности об объединениях:
В объединении стандартной компоновки с активным членом типа структуры T1
разрешается считывать нестатический член данных m
другого члена объединения типа структуры T2
если m является частью общей начальной последовательности T1 и T2; поведение такое, как если бы соответствующий член T1 был назначен.
Ваш код не соответствует. Зачем? Потому что вы не читаете "другого члена профсоюза". Вы делаете mx = 42;
, Это не чтение; что вызов функции члена другого члена профсоюза.
Так что это не соответствует общему правилу начальной последовательности. А без общего правила начальной последовательности, которое вас защищает, доступ к неактивным членам объединения является UB.