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


155

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

Answers:


124

像第一个示例一样使用它:

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值引用,则需要将左值转换(因为它现在有了名称!)变回右值引用。

但是,您不应该多次转发某些东西,因为通常这没有意义:转发意味着您可能会将参数一直移动到最终调用者,并且一旦移动了该参数,它就消失了,因此您就无法使用它了。再次(以您可能想要的方式)。


我以为是Args...&& args
小狗

5
@DeadMG:永远都是正确的,而不是我记错了的一个:-) ...尽管在这种情况下,我似乎记错了!
Kerrek SB 2011年

1
但是,对于通用类型T,如何声明g?
MK。

@MK。g被声明为具有所需参数的常规函数​​。
CoffeDeveloper

1
@cmdLP:正确地定义了它可以重复转发,但这对您的程序在语义上很少正确。但是,采用前向表达式的成员是一个有用的情况。我将更新答案。
Kerrek SB

4

Kerrek的答案非常有用,但是并不能完全回答标题中的问题:

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

为了回答这个问题,我们首先应该引入通用引用的概念。斯科特·迈耶斯(Scott Meyers)给出了这个名称,如今,它们通常被称为转发引用。基本上,当您看到类似以下内容的内容时:

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

请记住,这param不是一个右值引用(可能很容易得出结论),而是一个通用引用*。通用引用的特征是非常受限制的形式(just T&&,没有const或类似的限定词)和类型推导 -类型T将在f调用时被推导。简而言之,如果通用引用使用rvalues初始化,则它们对应于rvalue引用;如果使用lvalues初始化,则对应于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不合格模板参数采用右值引用的概念。


0

这个例子有帮助吗?我努力地找到了一个有用的非通用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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.