У меня есть 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
, не делая структуру нетривиально копируемой?