Присвойте значение изменчивой структуре, которая должна быть легко копируемой - PullRequest
2 голосов
/ 19 сентября 2019

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

Вот упрощенный пример того, что я пытаюсь сделать:

struct foo {
    uint16_t a;
    uint16_t b;
};

int main() {
    static_assert(std::is_trivially_copyable<foo>::value, "Oh no!");
    volatile foo vfoo;
    foo foo_value{10, 20};
    vfoo = foo_value;
}

Если я попытаюсь скомпилировать это с помощью g ++, произойдет сбой в строке vfoo = foo_value с сообщением «Ошибка: передача 'volatile foo', поскольку аргумент 'this' отбрасывает квалификаторы"Согласно этому вопросу , это связано с тем, что неявно определенный оператор присваивания не объявляется как volatile, и мне нужно определить оператор присваивания volatile для назначения для объекта volatile.Однако, если я сделаю это:

struct foo {
    uint16_t a;
    uint16_t b;
    volatile foo& operator=(const foo& f) volatile {
        if(this != &f) {
            a = f.a;
            b = f.b;
        }
        return *this;
    }
}

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

Посколькупо-видимому, компилятор решил, что мне не разрешено делать это очень простое действие, в настоящее время я использую этот обходной путь:

int main() {
    static_assert(std::is_trivially_copyable<foo>::value, "Oh no!");
    volatile foo vfoo;
    foo foo_value{10, 20};
    memcpy(const_cast<foo*>(&vfoo), &foo_value, sizeof(foo));
    std::atomic_signal_fence(std::memory_order_acq_rel);
}

Очевидно, что отбрасывать volatile не очень хорошая идея, потому чтоозначает, что компилятору теперь разрешено нарушать семантику, которую volatile должен был применять (т. е. каждое чтение и запись в коде транслируется в фактическое чтение или запись в память).Я попытался смягчить это, заменив присваивание на memcpy, что должно означать, что компилятор не может оптимизировать запись (даже если он считает, что запись не будет видна остальной части программы), и добавивзабор памяти после присваивания, что должно означать, что компилятор не может отложить запись намного позже.

Это лучшее, что я могу сделать?Есть ли лучший обходной путь, который приблизится к правильной семантике для volatile?Или есть способ заставить компилятор позволить мне присвоить новое значение структуре volatile, не делая структуру нетривиально копируемой?

1 Ответ

5 голосов
/ 19 сентября 2019

Если вам не обязательно использовать оператор присваивания (т. Е. Если вы считаете альтернативу memcpy жизнеспособной), то вместо этого вы можете написать неоператорную функцию присваивания:

volatile foo& volatile_assign(volatile foo& f, const foo& o) {
    if(&f != &o) {
        f.a = o.a;
        f.b = o.b;
    }
    return f;
}

Вы можете использоватьфункция-член, если вы так предпочитаете.

Я написал это на основе вашего примера, но подумайте, действительна ли проверка самоназначения относительно изменчивой семантики.Разве одни и те же значения не должны быть переписаны?Я не думаю, что случай даже действителен, если только объект на самом деле не является энергонезависимым, так как в противном случае мы будем читать энергозависимый объект через энергонезависимую ссылку (возможно, вам также понадобится логическая квалификация для другого операнда).

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