Bağımsız değişkenleri iletmek için std :: forward ne zaman kullanılır?


155

C ++ 0x aşağıdakilere bir örnek gösterir std::forward:

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

Ne zaman kullanmak std::forwardher zaman avantajlıdır ?

Ayrıca, &&parametre bildiriminde kullanılmasını gerektirir , her durumda geçerli mi? Ben fonksiyonu ile bildirilirse, bir fonksiyona geçici geçmesi gerektiğini düşündüm &&, bu yüzden herhangi bir parametre ile foo çağrılabilir?

Son olarak, böyle bir işlev çağrısı varsa:

template<int val, typename... Params>
void doSomething(Params... args) {
  doSomethingElse<val, Params...>(args...);
}

Bunun yerine kullanmalıyım:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
}

Ayrıca, parametreler işlevde iki kez kullanılırsa, yani aynı anda iki işleve yönlendiriliyorsa, kullanmak akıllıca std::forwardmıdır? Olmaz std::forwardhafızayı hareketli iki kez geçici aynı şeyi dönüştürmek ve ikinci bir kullanım için geçersiz hale? Aşağıdaki kod iyi olur:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
  doSomethingWeird<val, Params...>(std::forward<Params>(args)...);
}

Biraz kafam karıştı std::forwardve memnuniyetle biraz temizlik yapardım.

Yanıtlar:


124

İlk örneğiniz gibi kullanın:

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

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

Bunun nedeni, başvuru daraltma kurallarından kaynaklanmaktadır : If T = U&, then T&& = U&, but if T = U&&, then T&& = U&&, böylece her zaman işlev gövdesinin içinde doğru türle sonuçlanırsınız. Son olarak, forwardbaşlangıçta bir değerse, lvalue-dönükünü x(şimdi bir adı olduğu için!) Bir rvalue referansına geri döndürmeniz gerekir.

Bununla birlikte, bir şeyi bir kereden fazla iletmemelisiniz, çünkü bu genellikle mantıklı değildir: Yönlendirme , argümanı potansiyel olarak son arayan kişiye kadar taşıdığınız anlamına gelir ve taşındıktan sonra gitti, bu yüzden kullanamazsınız tekrar (muhtemelen demek istediğin şekilde).


Öyle Args...&& argsmi sanıyordum ?
Köpek yavrusu

5
@DeadMG: Her zaman doğru olan, yanlış anladığım değil :-) ... bu durumda doğru yanlış anladım gibi görünüyor!
Kerrek SB

1
Peki genel tip T için g nasıl bildirilir?
MK.

@MK. g, istediğiniz parametrelerle normal bir işlev olarak bildirilir.
Kahve

1
@cmdLP: Tekrar tekrar iletmenin iyi tanımlanmış olduğu konusunda haklısınız, ancak programınız için nadiren anlamsal olarak doğru. Bununla birlikte, ileri ifadelerin üyelerini almak yararlı bir durumdur. Cevabı güncelleyeceğim.
Kerrek SB

4

Kerrek'in cevabı çok faydalıdır, ancak soruya başlıktan tamamen cevap vermez:

Bağımsız değişkenleri iletmek için std :: forward ne zaman kullanılır?

Cevaplamak için önce evrensel referanslar kavramını tanıtmalıyız . Scott Meyers bu adı verdi ve bugünlerde genellikle yönlendirme referansları olarak adlandırılıyor. Temel olarak, böyle bir şey gördüğünüzde:

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

unutmayın ki param(bir sonuçlandırmak için cazip olabilir gibi) rvalue referans değil, evrensel bir referans *. Evrensel referanslar, çok kısıtlı bir formla (sadece T&&, sabit veya benzer niteleyiciler olmadan) ve tür kesinti ile karakterize edilir - tür çağrıldığında Tçıkarılır f. Özetle, evrensel referanslar, değerlerle başlatılırlarsa değer referanslarına ve değerlerle başlatılırlarsa değer referanslarına karşılık gelir.

Şimdi orijinal soruyu cevaplamak nispeten kolaydır - aşağıdakilere başvurun std::forward:

  • fonksiyonda son kullanıldığında evrensel referans
  • değere göre dönen işlevlerden döndürülen evrensel başvuru

İlk durum için bir örnek:

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
}

Yukarıdaki kodda , bittikten propsonra bilinmeyen bir değere sahip olmak istemiyoruz other.set(..), bu nedenle burada yönlendirme gerçekleşmiyor. Bununla birlikte, barbiz onu bitirirken ileriye doğru çağırırken propve onunla barne isterse yapabiliriz (örneğin taşıyın).

İkinci durum için bir örnek:

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

Bu işlev şablonu propbir değerse dönüş değerine hareket etmeli ve değerse kopyasını almalıdır . Sonunda ihmal std::forwardedersek, daima bir kopya oluştururuz, bu propda bir rvalue olduğunda daha pahalıdır .

* tam olarak açık olmak gerekirse, evrensel bir referans bir cv-kalifiye olmayan şablon parametresine bir rvalue referansı alma kavramıdır.


0

Bu örnek yardımcı olur mu? Std :: forward'in kullanışlı olmayan jenerik olmayan bir örneğini bulmak için mücadele ettim, ancak argüman olarak yatırılacak parayı aktardığımız bir banka hesabı örneğine çarptım.

Bu nedenle, bir hesabın const sürümüne sahipsek, hesabı mevduat şablonumuza <> geçirdiğimizde const işlevinin çağrılmasını beklemeliyiz; ve bu bir istisna atar (bunun kilitli bir hesap olduğu fikri!)

Sabit olmayan bir hesabımız varsa, hesabı değiştirebilmemiz gerekir.

#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;
    }
}

İnşa etmek:

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

Beklenen çıktı:

# 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.