【问题标题】:When to use std::forward to forward arguments?何时使用 std::forward 转发参数?
【发布时间】:2011-11-07 14:24:02
【问题描述】:

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

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

什么时候最好使用std::forward,总是这样?

另外,它需要在参数声明中使用&amp;&amp;,它在所有情况下都有效吗?我认为如果函数中声明了&amp;&amp;,则必须将临时变量传递给函数,那么可以使用任何参数调用 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)...);
}

我对@9​​87654332@ 有点困惑,我很乐意做一些清理工作。

【问题讨论】:

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


【解决方案1】:

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

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&amp;,那么T&amp;&amp; = U&amp;,但是如果T = U&amp;&amp;,那么T&amp;&amp; = U&amp;&amp;,所以你总是在函数体内得到正确的类型。最后,您需要forward 将左值转换的x(因为它现在有了名字!)重新转换为右值引用(如果它最初是一个)。

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

【讨论】:

  • 我还以为是Args...&amp;&amp; args
  • @DeadMG:总是正确的,而不是我记错的 :-) ...尽管在这种情况下,我似乎记错了!
  • 但是 g 是如何为泛型类型 T 声明的呢?
  • 在一个函数中多次转发一个变量是完全合法的。例如。如果你想转发一个结构的成员,你可以在结构上使用std::forward两次,如果你然后访问被转发的结构的成员,你将结构的一部分移动到新的地方,结构的成员得到清空,例如std::string。转发或移动永远不会使结构无效。也只是将std::move(my_string);std::forward&lt;T&gt;(val); 写为单个语句是没有用的。
  • @cmdLP:你说得对,重复转发的定义是正确的,但它对你的程序来说很少在语义上是正确的。不过,采用前向表达式的成员是一个有用的例子。我会更新答案。
【解决方案2】:

Kerrek 的回答很有用,但并没有完全回答标题中的问题:

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

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

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

请记住,param 不是右值引用(可能有人会想得出结论),而是通用引用*。通用引用的特点是非常受限的形式(只是 T&amp;&amp;,没有 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
}

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

第二种情况的例子:

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

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

*确切地说,通用引用是一个将右值引用用于 cv 非限定模板参数的概念。

【讨论】:

    【解决方案3】:

    这个例子有帮助吗?我努力寻找一个有用的非通用 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
    

    【讨论】:

      猜你喜欢
      • 2019-04-11
      • 2017-10-23
      • 2020-10-23
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 1970-01-01
      • 1970-01-01
      • 2014-01-04
      相关资源
      最近更新 更多