何时使用 std: : 转发到转发参数?

C + + 0x 展示了一个使用 std::forward的示例:

template<class T>
void foo(T&& arg)
{
bar(std::forward<T>(arg));
}

什么时候使用 std::forward总是有好处的?

此外,它需要在参数声明中使用 &&,它在所有情况下都有效吗?我认为,如果函数的声明中包含 &&,那么必须将临时变量传递给函数,所以可以用任何参数调用 foo 吗?

最后,如果我有一个这样的函数调用:

template<int val, typename... Params>
void doSomething(Params... args) {
doSomethingElse<val, Params...>(args...);
}

我应该用这个代替吗:

template<int val, typename... Params>
void doSomething(Params&&... args) {
doSomethingElse<val, Params...>(std::forward<Params>(args)...);
}

另外,如果在函数中使用两次参数,即同时转发到两个函数,那么使用 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)...);
}

我对 std::forward有点困惑,我很乐意使用一些清理。

73576 次浏览

就像你的第一个例子:

template <typename T> void f(T && x)
{
g(std::forward<T>(x));
}


template <typename ...Args> void f(Args && ...args)
{
g(std::forward<Args>(args)...);
}

这是因为 参考折叠规则: 如果 T = U&,那么 T&& = U&,但是如果 T = U&&,那么 T&& = U&&,所以你总是在函数体内得到正确的类型。最后,您需要 forward来转换由左值转换的 x(因为它现在有名称了!)如果最初是右值引用,则返回到右值引用。

然而,你不应该转发一些东西超过一次,因为这通常是没有意义的: 转发意味着你可能是 移动的参数一直到最后一个调用者,一旦它移动了它走了,所以你不能再次使用它(以你可能想要的方式)。

克雷克的回答非常有用,但它并没有完全回答题目中的问题:

何时使用 std: : 转发到转发参数?

为了回答这个问题,我们首先应该引入 通用参考文献的概念。Scott Meyers 给了这个名字,现在它们通常被称为转发引用。基本上,当你看到这样的东西:

template<typename T>
void f(T&& param);

请记住,param不是一个右值参考(人们可能会得出这样的结论) ,而是一个普遍参考 * 。通用引用的拥有属性非常有限(只有 T&&,没有 const 或类似的限定符) ,而通过 类别扣除,当调用 f时,就可以推断出类型 T。简而言之,通用引用对应于初始化它们的右值引用 如果引用是用 lvalue 初始化的,则为 lvalue 引用。

现在回答最初的问题相对容易了——将 std::forward应用于:

  • 最后一次在函数中使用的通用引用
  • 从按值返回的函数返回的通用引用

第一种情况的一个例子:

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
}

在上面的代码中,我们不希望 propother.set(..)完成之后有一些未知的值,所以这里不会发生转发。然而,当我们调用 bar时,我们转发 prop,因为我们已经完成了它,而 bar可以对它做任何它想做的事情(例如移动它)。

第二种情况的一个例子:

template<typename T>
Widget transform(T&& prop) {
prop.transform();
return std::forward<T>(prop);
}

这个函数模板应该将 prop移动到返回值(如果是右值)中,如果是左值,则复制它。如果我们在最后省略了 std::forward,我们将始终创建一个副本,当 prop恰好是一个右值时,这个副本的开销会更大。

* 为了完全精确,通用引用是一个对 cv 非限定模板参数进行右值引用的概念。

这个例子有帮助吗?我很难找到一个有用的非通用的 std: : forward 示例,但偶然发现了一个我们传递的银行账户示例 作为论据存入的现金。

因此,如果我们有一个 const 版本的帐户,我们应该期望当我们传递给我们的存款模板 < > 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;
}
}

建造:

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

预期产出:

# create account1 and try to deposit into it
new cash BankAccount(0x7ffee68d96b0, cash $0)
deposit cash called BankAccount(0x7ffee68d96b0, cash $100)
BankAccount(0x7ffee68d96b0, cash $100)
# SUCCESS: account1 deposit succeeded!
delete account BankAccount(0x7ffee68d96b0, cash $100)


# create locked account2 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9670, cash $0)
delete account BankAccount(0x7ffee68d9670, cash $0)
# 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 $0)
delete account BankAccount(0x7ffee68d9630, cash $0)
# FAILED: account3 deposit failed!: tried to write to a locked (const) account