提问人:danry 提问时间:5/9/2023 最后编辑:cigiendanry 更新时间:5/9/2023 访问量:148
将 std::move shared_ptr与条件运算符一起使用时出现奇怪的行为
Weird behavior when using std::move shared_ptr with conditional operator
问:
我正在使用 on 处理一些 C++ 代码,并得到了非常奇怪的输出。我简化了我的代码,如下所示std::move
shared_ptr
int func(std::shared_ptr<int>&& a) {
return 0;
}
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(1);
for (int i = 0; i != 10; ++i) {
func(i == 9 ? std::move(ptr) : std::shared_ptr<int>(ptr));
}
if (ptr) {
std::cout << "ptr is not null: " << *ptr << "\n";
} else {
std::cout << "ptr is null\n";
}
return 0;
}
我得到了输出
ptr is null
正如我所料,我的意志在最后一个循环中被移动(强制转换为),并且由于从不窃取内存,我的外部将是非空的(结果实际上是空的)。如果我替换ptr
std::shared_ptr<int>&&
func
a
ptr
func(i == 9 ? std::move(ptr) : std::shared_ptr<int>(ptr));
使用 if-else 语句
if (i == 9) func(std::move(ptr));
else func(std::shared_ptr<int>(ptr));
输出将是
ptr is not null: 1
我对编译器的这种行为感到非常困惑。
我尝试了具有不同 std 版本和优化级别的 GCC 和 clang,并得到了相同的输出。有人可以为我解释为什么以及下面的数据在哪里被盗吗?ptr
答:
由于条件运算符的第二个和第三个操作数不具有相同的值类别(即,是 xvalue,而 prvalue),因此此条件表达式属于 [expr.cond]/7:std::move(ptr)
std::shared_ptr<int>(ptr)
左值到右值、数组到指针和函数到指针的标准转换在第二和第三操作数上执行。 在这些转换之后,以下其中一项应成立:
- 第二和第三操作数具有相同的类型;结果是该类型的,并且使用选定的操作数初始化结果对象。
- [...]
std::shared_ptr<int>(ptr)
已经是一个 PRVALUE,因此左值到右值的转换(实际上是 glvalue 到 PRVALUE 的转换)对它没有任何作用。
std::move(ptr)
转换为 PR值,并且该 PR值用于初始化结果对象。结果对象的初始化使用 move 构造函数(因为这是从该类型的 xvalue 初始化 a 的构造函数,这就是它)。move 构造函数从 中“窃取”值。result 对象是一个临时对象,它绑定到参数,然后被销毁。请注意,所有这些都只发生在实际评估的情况下(这需要为真)。std::shared_ptr<int>
std::move(ptr)
ptr
a
std::move(ptr)
i == 9
评论
调用的 C++ 函数不需要移出值,即使使用右值引用调用它们也是如此。特别是,你甚至没有触及它的参数,所以在这个调用中保持不变:。func()
a
ptr
func(std::move(ptr))
相反,你的表情
func(i == 9 ? std::move(ptr) : std::shared_ptr<int>(ptr));
始终在调用之前创建一个 temporary 类型,并使用对该 temporary 的右值引用。引擎盖下发生的事情是:std::shared_ptr<int>
func()
{
std::shared_ptr<int> temp = i == 9 ? std::move(ptr) : std::shared_ptr<int>(ptr));
func(std::move(temp));
}
当为 9 时,赋值将移至空并留空。然后,当到达块的末尾并且临时超出范围时,它就会被销毁。i
temp = std::move(ptr)
ptr
temp
ptr
您可以通过将行更改为以下两种变体之一来避免这种行为:
func(i == 9 ? std::move(ptr) : std::move(std::shared_ptr<int>(ptr)));
艺术
func(std::move(i == 9 ? ptr : std::shared_ptr<int>(ptr)));
这两种变体都确保临时创建的是引用类型(即在第一种情况下和在第二种情况下),因此不需要复制任何内容。std::shared_ptr<int>&&
std::shared_ptr<int>&
你可能会问,为什么原始代码会创建一个临时类型(而不是 )。嗯,原因是众所周知的返回值优化(RVO),它在几十年前被引入C++:返回对象的函数实际上是以调用函数已经为该对象分配空间并将指向该对象的指针传递给被调用函数的方式调用的。然后,被调用的函数将返回值直接写入该空间。正因为如此,许多直接任务,例如:std::shared_ptr<int>
std::shared_ptr<int>&&
auto x = function(a, b, c);
省去了先创建临时值然后立即再次销毁临时值的麻烦。但是在你的三元运算符的情况下,原本隐藏的临时突然变得栩栩如生。
评论
std::shared_ptr<int>
std::shared_ptr<int>&
上一个:模板副本分配功能的混淆
评论
std::shared_ptr<int>
func
ptr
func
func
std::shared_ptr<int>