为必须是平凡可复制的volatile结构分配一个值

Edw*_*ard 2 c++ volatile c++17

我有一个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;
}
Run Code Online (Sandbox Code Playgroud)

如果我尝试使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,静态断言将失败,因为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);
}
Run Code Online (Sandbox Code Playgroud)

显然,抛弃这volatile不是一个好主意,因为这意味着现在允许编译器违反volatile应该执行的语义(即,将代码中的每次读取和写入都转换为实际的读取或写入内存)。我试图通过用来代替赋值来减轻这种情况memcpy,这应该意味着编译器无法优化写操作(即使它认为该写操作对于程序的其余部分也不可见),并添加了内存屏障在分配之后,这意味着编译器不能选择将写入延迟到很晚。

这是我能做的最好的吗?是否有更好的解决方法更接近正确的语义volatile?还是有办法让编译器让我为volatile结构分配一个新值,而又不使该结构不可复制?

eer*_*ika 5

如果您不一定需要使用赋值运算符(即,如果您认为memcpy替代方法可行),则可以编写一个非运算符赋值函数:

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

如果愿意,可以使用成员函数。

我是根据您的示例编写的,但是考虑到自赋值检查对于可变语义是否有效。难道不应该重写相同的值吗?除非对象实际上是非易失性的,否则我认为这种情况甚至都不成立,因为否则我们将通过非易失性引用读取易失性对象(也许您还需要其他操作数的易失性限定)。