简体   繁体   English

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

[英]When to use std::forward to forward arguments?

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, ie forwarding to two functions at the same time, is it wise to use std::forward ?另外,如果在函数中两次使用参数,即同时转发到两个函数,使用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?不会std::forward两次将同一事物转换为临时对象,移动内存并使其在第二次使用时无效? Would the following code be ok:下面的代码是否可以:

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有点困惑,我很乐意使用一些清理方法。

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.这是因为 引用折叠规则:如果T = U& ,则T&& = U& ,但如果T = U&& ,则T&& = U&& ,所以你总是在函数体内得到正确的类型。 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.最后,您需要forward将左值转换的x (因为它现在有一个名称!)如果它最初是一个右值引用。

You should not forward something more than once however, because that usually does not make sense: Forwarding means that you're potentially moving the 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).但是,您不应多次转发某些内容,因为这通常没有意义:转发意味着您可能参数一直移动到最终调用者,一旦移动它就消失了,因此您不能再使用它再次(以您可能打算的方式)。

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. Scott Meyers 给了这个名字,现在他们通常被称为转发引用。 Basically, when you see something like this:基本上,当你看到这样的东西时:

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*.请记住, param不是一个右值引用(因为人们可能会得出结论),而是一个通用引用*。 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.通用引用的特点是非常受限制的形式(只有T&& ,没有 const 或类似的限定符)和类型推导- 调用f时将推导出类型T 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.简而言之,通用引用对应于用右值初始化的右值引用,如果它们用左值初始化则对应于左值引用。

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.在上面的代码中,我们不希望在other.set(..)完成后prop有一些未知的值,所以这里没有转发。 However, when calling bar we forward prop as we are done with it and bar can do whatever it wants with it (eg move it).然而,当调用bar时,我们会在处理prop转发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.如果它是一个右值,这个函数模板应该将prop移动到返回值中,如果它是一个左值,则复制它。 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.如果我们最后省略了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 模板参数进行右值引用的概念。

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;因此,如果我们有一个 const 版本的帐户,当我们将它传递给我们的存款模板<>时,我们应该期望调用 const 函数; and this then throws an exception (the idea being this was a locked account!)然后这会抛出一个异常(这个想法是一个锁定的帐户!)

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 $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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM