[英]When to use std::forward to forward arguments?
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
有点困惑,我很乐意使用一些清理方法。
像你的第一个例子一样使用它:
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
(因为它现在有一个名称!)如果它最初是一个右值引用。
但是,您不应多次转发某些内容,因为这通常没有意义:转发意味着您可能将参数一直移动到最终调用者,一旦移动它就消失了,因此您不能再使用它再次(以您可能打算的方式)。
Kerrek 的回答很有用,但并没有完全回答标题中的问题:
何时使用 std::forward 转发参数?
为了回答这个问题,我们首先应该引入一个通用引用的概念。 Scott Meyers 给了这个名字,现在他们通常被称为转发引用。 基本上,当你看到这样的东西时:
template<typename T>
void f(T&& param);
请记住, param
不是一个右值引用(因为人们可能会得出结论),而是一个通用引用*。 通用引用的特点是非常受限制的形式(只有T&&
,没有 const 或类似的限定符)和类型推导- 调用f
时将推导出类型T
简而言之,通用引用对应于用右值初始化的右值引用,如果它们用左值初始化则对应于左值引用。
现在回答原始问题相对容易 - 将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
}
在上面的代码中,我们不希望在other.set(..)
完成后prop
有一些未知的值,所以这里没有转发。 然而,当调用bar
时,我们会在处理prop
转发prop
,并且bar
可以对它做任何想做的事情(例如移动它)。
第二种情况的示例:
template<typename T>
Widget transform(T&& prop) {
prop.transform();
return std::forward<T>(prop);
}
如果它是一个右值,这个函数模板应该将prop
移动到返回值中,如果它是一个左值,则复制它。 如果我们最后省略了std::forward
,我们总是会创建一个副本,当prop
恰好是一个右值时,这会更昂贵。
*完全准确地说,通用引用是对 cv-unqualified 模板参数进行右值引用的概念。
这个例子有帮助吗? 我努力寻找一个有用的 std::forward 的非通用示例,但找到了一个银行账户的示例,我们将要存入的现金作为参数传递。
因此,如果我们有一个 const 版本的帐户,当我们将它传递给我们的存款模板<>时,我们应该期望调用 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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.