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

时间:2011-08-31 12:52:47

标签: c++ templates forwarding c++11

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感到有些困惑,我很乐意使用一些清理工作。

3 个答案:

答案 0 :(得分:114)

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

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

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

那是因为reference collapsing rules:如果是T = U&,那么T&& = U&,但如果是T = U&&,那么T&& = U&&,那么你总是得到正确的结果在函数体内输入。最后,您需要forward将左值变为x(因为它现在有一个名字!),如果最初是左值,则将其转回右值引用。

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

答案 1 :(得分:4)

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

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

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

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

请记住,param不是右值引用(可能会尝试得出结论),而是通用引用*。通用引用具有非常受限制的形式(只是T&&,没有const或类似的限定词)和类型推断的特征-类型T将在{{1}时被推断出来}被调用。简而言之,如果通用引用用初始化,则它们对应于右值引用 右值,如果是左值引用,则为左值引用。

现在,回答原始问题相对容易-将f应用于:

  • 上次在功能中使用通用参考
  • 从按值返回的函数中返回的通用引用

第一种情况的示例:

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 } 完成后prop具有一些未知值,因此此处没有转发。但是,在调用other.set(..)时,我们会按照完成的方式转发bar,而prop可以用它做任何想做的事情(例如,移动它)。

第二种情况的示例:

bar

如果此函数模板是右值,则应将template<typename T> Widget transform(T&& prop) { prop.transform(); return std::forward<T>(prop); } 移到返回值中,如果是左值,则应将其复制。如果我们最后省略了prop,我们将始终创建一个副本,当std::forward恰好是右值时,它会更昂贵。

*确切地说,通用引用是对cv不合格模板参数采用右值引用的概念。

答案 2 :(得分: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