c++ 我怎样才能让volatile结构体在赋值时表现得和volatile int完全一样?

n7taea2i  于 2022-12-24  发布在  其他
关注(0)|答案(2)|浏览(126)

当从一个非易失性的int赋值给一个volatile int时,编译器。当从一个相同类型的非易失性的struct赋值给一个volatile struct时,编译器看起来非常不高兴。
考虑下面的简单程序。

struct Bar {
    int a;
};

volatile int foo;
int foo2;

volatile Bar bar;
Bar bar2;

int main(){
    foo = foo2;
    bar = bar2;
}

当我试图编译这段代码时,我在main的第二行得到了一个错误,而不是第一行。

g++     Main.cc   -o Main
Main.cc: In function ‘int main()’:
Main.cc:13:9: error: passing ‘volatile Bar’ as ‘this’ argument discards qualifiers [-fpermissive]
     bar = bar2;
         ^
Main.cc:1:8: note:   in call to ‘Bar& Bar::operator=(const Bar&)’
 struct Bar {

出现这个问题似乎是因为volatile Bar被传递到赋值运算符的左侧,尽管我不确定为什么这对int来说不是问题。
我查看了this answer,它建议进行以下修复。

struct Bar {
    int a;
    volatile Bar& operator= (const Bar& other) volatile {
       *this = other; 
    }
};

不幸的是,这导致了以下两个警告。

g++     Main.cc   -o Main
Main.cc: In member function ‘volatile Bar& Bar::operator=(const Bar&) volatile’:
Main.cc:4:21: warning: implicit dereference will not access object of type ‘volatile Bar’ in statement
        *this = other; 
                     ^
Main.cc: In function ‘int main()’:
Main.cc:16:15: warning: implicit dereference will not access object of type ‘volatile Bar’ in statement
     bar = bar2;

然后我查看了this answer,它提到我应该 * 将引用强制转换为右值 *,但是我不确定要强制转换哪个引用,以及在这种情况下使用哪种强制转换语法。

要使main的第2行上的赋值语句与main的第1行上的赋值语句完全相同,并且没有警告或错误,正确的咒语是什么?

jgwigjjp

jgwigjjp1#

您最初的问题是因为隐式赋值运算符具有签名

Bar& operator=(const Bar& rhs);

...并且对于volatile对象是不可调用的。警告是因为你的更新函数返回了一个volatile引用,但是这个引用从未被使用过。GCC认为这可能是个问题。解决这个问题的最简单的方法是将返回类型改为void!
还有一个问题:你的函数将在无限递归中调用自己。我建议如下:

struct Bar {
    int a;
    Bar& operator=(const Bar&rhs) = default;
    void operator=(const volatile Bar& rhs) volatile // Note void return.
    {
         // Caution: This const_cast removes the volatile from
         // the reference.  This may lose the point of the original
         // volatile qualification.
         //
         // If this is a problem, use "a = rhs.a;" instead - but this
         // obviously doesn't generalize so well.
         const_cast<Bar&>(*this) = const_cast<const Bar&>(rhs);
    }
};

volatile Bar vbar;
Bar bar;

int main(){
    vbar = bar;  // All four combinations work.
    bar = vbar;
    vbar = vbar;
    bar = bar;
    return 0;
}

这意味着当使用volatile结构体时,你将不能链接赋值操作符。我Assert这不是一个巨大的损失。
最后旁白:为什么要使用volatile-它对多线程代码不是很有用(它对内存MapIO很有用)。

ztigrdn8

ztigrdn82#

@martin很好地描述了问题发生的原因,但没有提供不丢弃volatile的通用解决方案。
如果可以抛弃volatile,那么应该在赋值之前在函数外部完成,而将其隐藏在复制构造函数中会留下更多不可预见的bug。
稍微调整解决方案,可得到以下更通用的解决方案:

struct Bar {
    int a;
    Bar& operator=(const Bar&rhs) = default;
    auto & operator=(const volatile Bar& rhs) volatile
    {
         // Solution 1: depends on struct definition and can't be copied to many
         static_assert(sizeof(*this) == sizeof(a), "struct elements added or changed. update below");
         a = rhs.a;

         // Solution 2: very general solution that could be used if copying to many structs
         // Do default byte-by-byte copy operation, but can't use memcpy for volatile
         static_assert(is_trivially_copy_assignable_v<remove_cvref_t<decltype(*this)>>>, "Byte-by-byte copy may not be possible with this class");
         std::copy_n(reinterpret_cast<const volatile std::byte*>(&rhs), sizeof(*this), reinterpret_cast<volatile std::byte*>(this));

         // Support chaining of assignments
         //return *this;  // Okay, but returning a volatile will generate spurious warnings if that value is not read afterword
         return rhs; // assumes that you want to just chain assignments and not do anything weird
    }
};

这个版本的复制构造函数适用于volatile/non-volatile源和目标的所有4种组合,但是,如果您希望针对每种情况进行优化,也可以将其拆分为不同的组合。

相关问题