c++ std::的这种行为是move-only类型所期望的MSVC bug还是未定义的行为?

7qhs6swi  于 11个月前  发布在  其他
关注(0)|答案(2)|浏览(104)

在使用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
任何见解或解释将不胜感激!

wvmv3b1j

wvmv3b1j1#

在你的移动构造函数this->val是未初始化的。因此std::swapother.val设置为这个未初始化的值。MSVC的调试运行时将未初始化的内存设置为0xCCCCCCCC,所以这是打印的值。你可以通过添加一个成员初始化器在GCC中看到同样的行为:https://gcc.godbolt.org/z/hMa9Ka9cb
Move构造函数可以用std::exchange来实现,以帮助避免这个问题:

UniqueHandle(UniqueHandle&& other): val{std::exchange(other.val, nullptr)} { 
}

字符串
通过始终使用成员初始化器,可以更清楚地表明所有成员在使用前都已初始化,并且明确声明了moved from成员的新值。

w9apscun

w9apscun2#

公认的答案是完美的解决方案。但在一般情况下,我会使用default+swap成语(我自己编的成语):

void UniqueHandler::swap(UniqueHandler&);
UniqueHandler::UniqueHandler(UniqueHandler&& rvalue)
: UniqueHandler{/*delegate to default*/}
{ this->swap(rvalue); }; //swap

字符串
这是一个委托构造函数的用例。

相关问题