如何使易失性结构体在赋值过程中表现得完全像易失性int
How can I make a volatile struct behave exactly like a volatile int during assignment?
当一个非易失性的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
来说不是问题。
我看了这个答案,它建议以下修复。
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;
然后我看了看这个答案,它提到我应该将引用强制转换为右值,但是我不确定在这种情况下应该转换哪个引用,以及使用哪种强制转换语法。
什么是正确的咒语,使main
的第2行上的赋值行为完全像main
的第1行一样,没有警告或错误?
您最初的问题是因为隐式赋值操作符具有签名
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
-它不是很有用的多线程代码(它是有用的内存映射IO)
@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
}
};
这个版本的复制构造函数适用于所有4种易失性/非易失性源和目标的组合,但是,如果您希望对每种情况进行优化,您也可以将其分成不同的组合。
- 易失性sig_atomic_t的内存安全性
- C++易失性:保证 32 位访问?
- 避免易失性和非易失性成员函数的代码重复
- 当 2 个线程共享同一物理内核时,具有错误共享的易失性增量在发布中的运行速度比在调试中慢
- 如何访问常量易失性 std::array?
- 为什么在 C++20 中弃用易失性?
- 根据 MSVC,具有易失性成员的结构不再是 POD
- 是否允许编译器优化掉局部易失性变量
- 访问共享内存而不使用易失性、std::atomic、信号量、互斥锁和自旋锁
- 如何避免对无锁程序使用易失性?
- C++:易失性实例中的易失性成员函数 - 将数组分配给指针是无效的转换?
- g++ 6.3,avx 内联函数上的 Kahan 求和用易失性关键字进行序列化
- 是什么让这种易失性打破了结构的指针算法?
- 如果不需要易失性,为什么 std::atomic 方法会提供易失性重载
- *(易失性无符号整数 *) 的含义 0x00 = 0x00;
- 使用易失性 c 字符串和 std::cout
- 为什么对易失性 int 的常量引用需要static_cast
- 为什么我们不能用右值易失性 int&&&初始化对 const int 的引用?
- 如何使易失性结构体在赋值过程中表现得完全像易失性int
- 传递易失性 std 时出错::队列<int>丢弃限定符 [-允许] C++98