英文:
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); // 将由f()返回的xvalue移动到调用者
}
else {
return ret; // 返回普通值或左值引用
}
}
因为第一段代码"可能禁用移动语义和复制省略。对于普通值,这就像在返回语句中多余地使用std::move()"。这两种模式之间有什么区别?在我看来,对于普通值,decltype
将仅推断类型本身,因此它只是static_cast<Type>(ret)
(即根本没有操作),返回类型与声明的类型相同,因此复制省略是可能的。我是否有任何错误理解?
英文:
Sometimes we may defer perfect returning like this:
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);
}
But in Jousttis's new book C++ Move Semantics - The Complete Guide, he says that code below is better:
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
}
}
Because the first piece of code "might disable move semantics and copy elision. For plain
values, it is like having an unnecessary std::move() in the return statement." What's the difference between these two patterns? From my point of view, for plain values, decltype
will deduce just the type itself, so it's just a static_cast<Type>(ret)
(i.e. no operation at all) and the returned type is same as the declared type so that copy elision is possible. Is there anything that I take wrong?
答案1
得分: 4
我不知道你有什么版本的书,但我的书明确说明:
完美的返回,但是不必要的复制
然而,对于引用,问题并不会出现,但当涉及按值返回时,问题就会出现。考虑以下代码:
#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();
}
createSNoElide()
将被强制使用复制构造函数。根据标准,这很可能是由于以下部分:
这种复制/移动操作的省略,称为复制省略,允许在以下情况下(可以组合以消除多个副本):
在具有类返回类型的函数中的返回语句中,当表达式是具有自动存储期的非易失性对象的名称(除了函数参数或异常处理程序的异常声明引入的变量)并且具有与函数返回类型相同类型(忽略cv-资格),可以通过将对象直接构造到函数调用的返回对象来省略复制/移动操作。
即,唯一发生省略的方式是返回本地变量的名称。类型转换只是一种不同类型的表达式,它有效地阻止了省略,尽管这可能不太直观。
另外,我要额外感谢这篇文章:https://stackoverflow.com/a/55491382/4885321,它引导我到达了标准中的确切位置。
英文:
I don't know what edition of the book you have, but mine states explicitly:
> perfect return but unnecessary copy
The problem does not manifest for references, however it does when returning by value comes into play.
Consider the following code:
#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
The createSNoElide()
will be forced to use a copy constructor. Standard-wise it is most likely due to the following part:
> This elision of copy/move operations, called copy elision, is
> permitted in the following circumstances (which may be combined to
> eliminate multiple copies):
>
> in a return statement in a function with a class return type, when the
> expression is the name of a non-volatile object with automatic storage
> duration (other than a function parameter or a variable introduced by
> the exception-declaration of a handler ([except.handle])) with the
> same type (ignoring cv-qualification) as the function return type, the
> copy/move operation can be omitted by constructing the object directly
> into the function call's return object
>
https://eel.is/c++draft/class.copy.elision
I.e. the only way for elision to occur is to return a name of a local variable. Cast is simply a different type of expression, which effectively prevents elision, however counter-intuitive that might be.
Also, I should give extra credit to that post: https://stackoverflow.com/a/55491382/4885321 which guided me to the exact place in the standard.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论