提问人:dw218192 提问时间:11/13/2023 更新时间:11/13/2023 访问量:102
std::expected 的这种仅移动类型的行为是 MSVC bug 还是未定义的行为?
Is this behavior of std::expected with move-only types a MSVC bug or undefined behavior?
问:
使用 Microsoft Visual Studio 的 MSVC 编译器进行编译时,我的 C++ 代码遇到了一个奇怪的问题,我正在尝试根据 C++ 标准确定它是编译器错误还是未定义的行为。
最小可重现的例子如下:
#include <utility>
#include <iostream>
#include <memory>
#include <expected>
struct UniqueHandle {
UniqueHandle() {
val = nullptr;
}
~UniqueHandle() {
std::cout << "~UniqueHandle(" << val << ")" << std::endl;
}
UniqueHandle(void* val) : val(val) {}
UniqueHandle(UniqueHandle&& other) {
std::swap(val, other.val);
}
UniqueHandle& operator=(UniqueHandle&& other) {
std::swap(val, other.val);
return *this;
}
void* val;
};
std::expected<UniqueHandle, int> foo() {
return UniqueHandle {};
}
int main() {
auto exp_res = foo();
UniqueHandle result {};
if (exp_res) {
result = std::move(exp_res.value());
}
return 0;
}
该程序在调试版本中使用 VS 2022 工具链生成以下输出:
~UniqueHandle(CCCCCCCCCCCCCCCC)
~UniqueHandle(0000000000000000)
~UniqueHandle(0000000000000000)
在发布版本中,第一行是一些垃圾值。我看不出这个值是从哪里来的。但是,当由 g++ 编译时,它会按预期工作,这里是实时示例:
https://gcc.godbolt.org/z/zMh5W6MMj
任何见解或解释将不胜感激!
答:
3赞
Alan Birtles
11/13/2023
#1
在 move 中,构造函数未初始化。因此,设置为此未初始化的值。MSVC 的调试运行时将未初始化的内存设置为 0xCCCCCCCC
,因此这是打印的值。您可以通过添加成员初始值设定项来查看 GCC 中的相同行为: https://gcc.godbolt.org/z/hMa9Ka9cbthis->val
std::swap
other.val
可以使用 std::exchange
实现移动构造函数,以帮助避免此问题:
UniqueHandle(UniqueHandle&& other): val{std::exchange(other.val, nullptr)} {
}
通过始终使用成员初始值设定项,可以更清楚地看到所有成员在使用之前都已初始化,并且显式声明了从成员中移动的新值。
1赞
Red.Wave
11/13/2023
#2
公认的答案是完美的解决方案。 但是在一般情况下,我会使用 default+swap idiom(我编造的成语):
void UniqueHandler::swap(UniqueHandler&);
UniqueHandler::UniqueHandler(UniqueHandler&& rvalue)
: UniqueHandler{/*delegate to default*/}
{ this->swap(rvalue); }; //swap
这是委托构造函数的用例。
评论
I fail to see where this value comes from.
从损坏的移动构造函数实现中。 在与其他交换之前,您永远不会初始化它。UniqueHandle(UniqueHandle&& other) { std::swap(val, other.val); }
val
val
0xCCCCCCCCCCCCCCCC
MSVC 用于显示未初始化的内存。std::exchange
来表示移动的东西。它有助于避免错误。