提问人:o_oTurtle 提问时间:2/8/2023 更新时间:2/8/2023 访问量:110
为什么此代码可能会禁用移动语义和复制省略?
Why may this code disable move semantics and copy elision?
问:
有时我们可能会像这样推迟完美的回报:
template<typename Func, typename... Args>
decltype(auto) call(Func f, Args&&... args)
{
decltype(auto) ret{f(std::forward<Args>(args)...)};
// ...
return static_cast<decltype(ret)>(ret);
}
但在Jousttis的新书《C++ Move Semantics - The Complete Guide》中,他说下面的代码更好:
template<typename Func, typename... Args>
decltype(auto) call(Func f, Args&&... args)
{
decltype(auto) ret{f(std::forward<Args>(args)...)};
// ...
if constexpr (std::is_rvalue_reference_v<decltype(ret)>) {
return std::move(ret); // move xvalue returned by f() to the caller
}
else {
return ret; // return the plain value or the lvalue reference
}
}
因为第一段代码“可能会禁用移动语义和复制省略。对于普通
值,这就像在 return 语句中有一个不必要的 std::move()。这两种模式之间有什么区别?从我的角度来看,对于纯值,将只推断类型本身,所以它只是一个(即根本没有操作),返回的类型与声明的类型相同,因此可以进行复制省略。我有什么看错的地方吗?decltype
static_cast<Type>(ret)
答:
我不知道你有哪个版本的书,但我明确指出:
完美回归,但不必要的复制
该问题不会在引用中显现出来,但是当按值返回时,它就会显现出来。 请考虑以下代码:
#include <iostream>
struct S
{
S() { std::cout << "constr\n";}
S(const S& ) { std::cout << "copy constr\n"; }
S(S&& ) { std::cout << "move constr\n"; }
};
S createSNoElide()
{
S s;
return static_cast<decltype(s)>(s);
}
S createSElide()
{
S s;
return s;
}
int main(int, char*[])
{
std::cout << "Elision\n";
S s1 = createSElide();
std::cout << "No elision\n";
S s2 = createSNoElide();
}
https://godbolt.org/z/YqG54rM9E
将强制使用复制构造函数。从标准上讲,这很可能是由于以下部分:createSNoElide()
这种复制/移动操作的省略称为复制省略,是 允许在以下情况下(可以合并到 消除多个副本):
在具有类返回类型的函数的 RETURN 语句中,当 expression 是具有自动存储功能的非易失性对象的名称 duration (函数参数或由 处理程序 ([except.handle])) 的异常声明,其中包含 与函数返回类型相同的类型(忽略 cv-qualification),则 通过直接构造对象,可以省略复制/移动操作 放入函数调用的返回对象
https://eel.is/c++draft/class.copy.elision
即,发生省略的唯一方法是返回局部变量的名称。Cast 只是一种不同类型的表达,它有效地防止了省略,无论这可能违反直觉。
另外,我应该特别感谢那篇帖子:https://stackoverflow.com/a/55491382/4885321 引导我到达标准中的确切位置。
评论
static_cast<Type>(arg)
评论