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)...);
}

また、関数でパラメータを2回使用する場合、つまり2つの関数に同時に転送する場合、使用するのが賢明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ますが、片付けは喜んで行います。

回答:


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最初は右辺値参照であった場合は、右辺値参照に戻す必要があります。

ただし、通常は意味をなさないため、何かを2回以上転送しないでください。転送とは、最後の呼び出し元まで引数を移動する可能性があることを意味します。移動すると、引数がなくなるため、使用できなくなります。もう一度(おそらくあなたが意図した方法で)。


と思いましたArgs...&& argsか?
子犬

5
@DeadMG:それは常に正しいものであり、私が覚えていなかったものではありません:-) ...この場合、私は正しく覚えていなかったようです!
Kerrek SB、2011

1
しかし、ジェネリック型Tに対してgはどのように宣言されていますか
MK。

@MK。gは、必要なパラメーターを持つ通常の関数として宣言されます。
CoffeDeveloper 2015年

1
@cmdLP:繰り返し転送することは明確に定義されていますが、プログラムにとって意味的に正しいことはほとんどありません。ただし、前方式のメンバーを取るのは便利なケースです。答えを更新します。
Kerrek SB 2018

4

Kerrekの回答は非常に役に立ちますが、タイトルからの質問には完全には回答しません。

std :: forwardを使用して引数を転送するのはいつですか?

これに答えるために、まず普遍的な参照の概念を導入する必要があります。スコット・マイヤーズはこの名前をつけました、そして今日、彼らはしばしば転送参照と呼ばれます。基本的に、次のようなものが表示された場合:

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

paramこれは右辺値の参照ではない(結論に誘惑される可能性があるため)ではなく、普遍的な参照です*。ユニバーサル参照は、非常に制限された形式(T&&constまたは同様の修飾子なし)と型のT推定によって特徴付けられます- 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と、やりたいことは何でも実行できます(移動など)。

2番目のケースの例:

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.