Свойства с нулевой стоимостью с синтаксисом элемента данных - PullRequest
0 голосов
/ 10 февраля 2019

Я (заново?) Изобрел этот подход к свойствам с нулевой стоимостью с помощью синтаксиса члена данных.Под этим я подразумеваю, что пользователь может написать:

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?

Ответы [ 2 ]

0 голосов
/ 10 февраля 2019

Вот что говорит правило common-initial-sequence о объединениях :

В объединении стандартной компоновки с активным членом типа структуры T1 оноразрешено читать нестатический элемент данных m другого члена объединения типа структуры T2, если m является частью общей начальной последовательности T1 и T2;поведение такое, как если бы был назначен соответствующий член T1.

Ваш код не соответствует требованиям.Зачем?Потому что вы не читаете от "другого члена профсоюза".Вы делаете m.x = 42;.Это не чтение;это вызывает функцию-член другого члена объединения.

Так что это не соответствует общему правилу начальной последовательности.А без общего правила начальной последовательности для защиты вас доступ к неактивным членам объединения будет UB.

0 голосов
/ 10 февраля 2019

TL; DR Это UB.

[basic.life]

Аналогично, до начала срока службы объекта, но после хранениякоторый будет занимать объект, был выделен или, после того, как закончился срок службы объекта и до повторного использования или освобождения хранилища, которое занимал объект, может использоваться любое значение glvalue, которое ссылается на исходный объект, но только ограниченным образом.О строящемся или разрушаемом объекте см. [Class.cdtor].В противном случае такое glvalue относится к выделенному хранилищу, и использование свойств glvalue, которые не зависят от его значения, является четко определенным.Программа имеет неопределенное поведение, если: [...]

  • glvalue используется для вызова нестатической функции-члена объекта, или

По определению , неактивный член объединения не находится в пределах своего времени жизни.


Возможный обходной путь - использовать C ++ 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);
...