C++ 何时使用 std::forward 转发参数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7257144/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When to use std::forward to forward arguments?
提问by coyotte508
C++0x shows an example of using std::forward
:
C++0x 显示了一个使用示例std::forward
:
template<class T>
void foo(T&& arg)
{
bar(std::forward<T>(arg));
}
When is it advantageous to use std::forward
, always?
什么时候使用std::forward
, 总是有利的?
Also, it requires to use &&
in the parameters declaration, is it valid in all cases? I thought you had to pass temporaries to a function if the function was declared with &&
in it, so can foo be called with any parameter?
此外,它需要&&
在参数声明中使用,它在所有情况下都有效吗?我认为如果函数在其中声明,您必须将临时变量传递给函数&&
,那么可以使用任何参数调用 foo 吗?
Lastly, if I have a function call such as this:
最后,如果我有这样的函数调用:
template<int val, typename... Params>
void doSomething(Params... args) {
doSomethingElse<val, Params...>(args...);
}
Should I use this instead:
我应该改用这个吗:
template<int val, typename... Params>
void doSomething(Params&&... args) {
doSomethingElse<val, Params...>(std::forward<Params>(args)...);
}
Also, if use the parameters twice in the function, i.e. forwarding to two functions at the same time, is it wise to use std::forward
? Won't std::forward
convert the same thing to a temporary twice, moving the memory and make it invalid for a second use? Would the following code be ok:
另外,如果在函数中两次使用参数,即同时转发给两个函数,使用 是明智的std::forward
吗?不会std::forward
将相同的东西转换为临时两次,移动内存并使其在第二次使用时无效?下面的代码是否可以:
template<int val, typename... Params>
void doSomething(Params&&... args) {
doSomethingElse<val, Params...>(std::forward<Params>(args)...);
doSomethingWeird<val, Params...>(std::forward<Params>(args)...);
}
I'm a bit confused by std::forward
, and I'd gladly use some clearing up.
我对 有点困惑std::forward
,我很乐意使用一些清理方法。
采纳答案by Kerrek SB
Use it like your first example:
像你的第一个例子一样使用它:
template <typename T> void f(T && x)
{
g(std::forward<T>(x));
}
template <typename ...Args> void f(Args && ...args)
{
g(std::forward<Args>(args)...);
}
That's because of the reference collapsing rules: If T = U&
, then T&& = U&
, but if T = U&&
, then T&& = U&&
, so you always end up with the correct type inside the function body. Finally, you need forward
to turn the lvalue-turned x
(because it has a name now!) back into an rvalue reference if it was one initially.
那是因为引用折叠规则: If T = U&
, then T&& = U&
, but if T = U&&
, then T&& = U&&
, 所以你总是在函数体内得到正确的类型。最后,您需要forward
将左值转换x
(因为它现在有一个名称!)如果它最初是一个右值引用。
You should not forward something more than once however, because that usually does not make sense: Forwarding means that you're potentially movingthe argument all the way through to the final caller, and once it's moved it's gone, so you cannot then use it again (in the way you probably meant to).
但是,您不应多次转发某些内容,因为这通常没有意义:转发意味着您可能将参数一直移动到最终调用者,一旦移动它就消失了,因此您不能再使用它再次(以您可能打算的方式)。
回答by Miljen Mikic
Kerrek's answer is very useful, but it doesn't completely answer the question from the title:
Kerrek 的回答非常有用,但它并没有完全回答标题中的问题:
When to use std::forward to forward arguments?
何时使用 std::forward 转发参数?
In order to answer it, we should first introduce a notion of universal references. Scott Meyers gave this name and nowadays they are often called forwarding references. Basically, when you see something like this:
为了回答这个问题,我们首先应该引入一个通用引用的概念。Scott Meyers 给了这个名字,现在他们通常被称为转发引用。基本上,当你看到这样的东西时:
template<typename T>
void f(T&& param);
bear in mind that param
is not an rvalue reference (as one may be tempted to conclude), but a universal reference*. Universal references are characterized by a very restricted form (just T&&
, without const or similar qualifiers) and by type deduction- the type T
will be deduced when f
is invoked. In a nutshell, universal references correspond to rvalue references if they're initialized with
rvalues, and to lvalue references if they're initialized with lvalues.
请记住,这param
不是右值引用(因为人们可能会得出结论),而是通用引用*。通用引用的特点是非常有限的形式(只是T&&
,没有 const 或类似的限定符)和类型推导- 类型T
将在f
被调用时推导。简而言之,通用引用对应于用右值初始化的右值引用,如果它们用左值初始化则对应于左值引用。
Now it's relatively easy to answer the original question - apply std::forward
to:
现在回答原始问题相对容易 - 适用std::forward
于:
- a universal reference the last time it's used in the function
- a universal reference being returned from functions that return by value
- 上次在函数中使用时的通用引用
- 从按值返回的函数返回的通用引用
An example for the first case:
第一种情况的示例:
template<typename T>
void foo(T&& prop) {
other.set(prop); // use prop, but don't modify it because we still need it
bar(std::forward<T>(prop)); // final use -> std::forward
}
In the code above, we don't want prop
to have some unknown value after other.set(..)
has finished, so no forwarding happens here. However, when calling bar
we forward prop
as we are done with it and bar
can do whatever it wants with it (e.g. move it).
在上面的代码中,我们不想prop
在other.set(..)
完成后有一些未知的值,所以这里没有转发。然而,当bar
我们调用prop
它时,我们完成它并且bar
可以用它做任何它想做的事情(例如移动它)。
An example for the second case:
第二种情况的示例:
template<typename T>
Widget transform(T&& prop) {
prop.transform();
return std::forward<T>(prop);
}
This function template should move prop
into the return value if it's an rvalue and copy it if it's an lvalue. In case that we omitted std::forward
at the end, we would always create a copy, which is more expensive when prop
happens to be an rvalue.
prop
如果它是一个右值,这个函数模板应该移动到返回值中,如果它是一个左值,则复制它。如果我们std::forward
在最后省略,我们总是会创建一个副本,当prop
碰巧是一个右值时,这会更昂贵。
*to be fully precise, a universal reference is a concept of taking an rvalue reference to a cv-unqualified template parameter.
*完全准确地说,通用引用是对 cv-unqualified 模板参数进行右值引用的概念。
回答by Neil McGill
Does this example help? I struggled to find a useful non generic example of std::forward, but hit upon an example of a bank account that we pass along the cash to be deposited as an argument.
这个例子有帮助吗?我努力寻找一个有用的 std::forward 的非通用示例,但找到了一个银行账户的示例,我们将要存入的现金作为参数传递。
So if we have a const version of an account we should expect when we pass it to our deposit template<> that the const function is called; and this then throws an exception (the idea being this was a locked account!)
因此,如果我们有一个 const 版本的帐户,当我们将它传递给我们的存款模板<>时,我们应该期望调用 const 函数;然后这会抛出一个异常(这个想法是一个锁定的帐户!)
If we have a non const account then we should be able to modify the account.
如果我们有一个非 const 帐户,那么我们应该能够修改该帐户。
#include <iostream>
#include <string>
#include <sstream> // std::stringstream
#include <algorithm> // std::move
#include <utility>
#include <iostream>
#include <functional>
template<class T> class BankAccount {
private:
const T no_cash {};
T cash {};
public:
BankAccount<T> () {
std::cout << "default constructor " << to_string() << std::endl;
}
BankAccount<T> (T cash) : cash (cash) {
std::cout << "new cash " << to_string() << std::endl;
}
BankAccount<T> (const BankAccount& o) {
std::cout << "copy cash constructor called for " << o.to_string() << std::endl;
cash = o.cash;
std::cout << "copy cash constructor result is " << to_string() << std::endl;
}
// Transfer of funds?
BankAccount<T> (BankAccount<T>&& o) {
std::cout << "move cash called for " << o.to_string() << std::endl;
cash = o.cash;
o.cash = no_cash;
std::cout << "move cash result is " << to_string() << std::endl;
}
~BankAccount<T> () {
std::cout << "delete account " << to_string() << std::endl;
}
void deposit (const T& deposit) {
cash += deposit;
std::cout << "deposit cash called " << to_string() << std::endl;
}
friend int deposit (int cash, const BankAccount<int> &&account) {
throw std::string("tried to write to a locked (const) account");
}
friend int deposit (int cash, const BankAccount<int> &account) {
throw std::string("tried to write to a locked (const) account");
}
friend int deposit (int cash, BankAccount<int> &account) {
account.deposit(cash);
return account.cash;
}
friend std::ostream& operator<<(std::ostream &os, const BankAccount<T>& o) {
os << "$" << std::to_string(o.cash);
return os;
}
std::string to_string (void) const {
auto address = static_cast<const void*>(this);
std::stringstream ss;
ss << address;
return "BankAccount(" + ss.str() + ", cash $" + std::to_string(cash) + ")";
}
};
template<typename T, typename Account>
int process_deposit(T cash, Account&& b) {
return deposit(cash, std::forward<Account>(b));
}
int main(int, char**)
{
try {
// create account1 and try to deposit into it
auto account1 = BankAccount<int>(0);
process_deposit<int>(100, account1);
std::cout << account1.to_string() << std::endl;
std::cout << "SUCCESS: account1 deposit succeeded!" << std::endl;
} catch (const std::string &e) {
std::cerr << "FAILED: account1 deposit failed!: " << e << std::endl;
}
try {
// create locked account2 and try to deposit into it; this should fail
const auto account2 = BankAccount<int>(0);
process_deposit<int>(100, account2);
std::cout << account2.to_string() << std::endl;
std::cout << "SUCCESS: account2 deposit succeeded!" << std::endl;
} catch (const std::string &e) {
std::cerr << "FAILED: account2 deposit failed!: " << e << std::endl;
}
try {
// create locked account3 and try to deposit into it; this should fail
auto account3 = BankAccount<int>(0);
process_deposit<int>(100, std::move(account3));
std::cout << account3.to_string() << std::endl;
std::cout << "SUCCESS: account3 deposit succeeded!" << std::endl;
} catch (const std::string &e) {
std::cerr << "FAILED: account3 deposit failed!: " << e << std::endl;
}
}
To build:
构建:
cd std_forward
rm -f *.o example
c++ -std=c++2a -Werror -g -ggdb3 -Wall -c -o main.o main.cpp
c++ main.o -o example
./example
Expected output:
预期输出:
# create account1 and try to deposit into it
new cash BankAccount(0x7ffee68d96b0, cash ##代码##)
deposit cash called BankAccount(0x7ffee68d96b0, cash 0)
BankAccount(0x7ffee68d96b0, cash 0)
# SUCCESS: account1 deposit succeeded!
delete account BankAccount(0x7ffee68d96b0, cash 0)
# create locked account2 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9670, cash ##代码##)
delete account BankAccount(0x7ffee68d9670, cash ##代码##)
# FAILED: account2 deposit failed!: tried to write to a locked (const) account
# create locked account3 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9630, cash ##代码##)
delete account BankAccount(0x7ffee68d9630, cash ##代码##)
# FAILED: account3 deposit failed!: tried to write to a locked (const) account