कब उपयोग करें std :: आगे के तर्कों के आगे?


155

C ++ 0x उपयोग करने का एक उदाहरण दिखाता है std::forward:

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

जब यह std::forwardहमेशा उपयोग करने के लिए फायदेमंद है ?

इसके अलावा, यह &&पैरामीटर घोषणा में उपयोग करने की आवश्यकता है , क्या यह सभी मामलों में मान्य है? मुझे लगा कि आपको एक समारोह में अस्थायी तौर पर पास करना होगा अगर फ़ंक्शन को &&इसके साथ घोषित किया गया था , तो क्या फू को किसी भी पैरामीटर के साथ बुलाया जा सकता है?

अंत में, अगर मेरे पास फंक्शन कॉल है जैसे:

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, और मैं ख़ुशी से कुछ समाशोधन का उपयोग करूँगा।

जवाबों:


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: यह हमेशा सही होता है, न कि जिसे मैंने गलत समझा था :-) ... हालांकि इस मामले में मुझे लगता है कि यह सही ढंग से गलत है!
केरेक एसबी

1
लेकिन सामान्य प्रकार टी के लिए जी कैसे घोषित किया जाता है?
एम.के.

@MK। जी को एक नियमित फ़ंक्शन के रूप में घोषित किया जाता है जो आप चाहते हैं।
कॉफिडेक्टर

1
@ सीएमडीएलपी: आप सही कह रहे हैं कि इसे बार-बार आगे बढ़ाने के लिए अच्छी तरह से परिभाषित किया गया है, लेकिन यह शायद ही कभी आपके कार्यक्रम के लिए सही है। हालांकि, आगे के भावों के सदस्यों को लेना एक उपयोगी मामला है। मैं जवाब अपडेट कर दूंगा।
केरेक एसबी

4

केरेक का उत्तर बहुत उपयोगी है, लेकिन यह शीर्षक से प्रश्न का पूरी तरह से उत्तर नहीं देता है:

कब उपयोग करें std :: आगे के तर्कों के आगे?

इसका उत्तर देने के लिए, हमें पहले सार्वभौमिक संदर्भों की एक धारणा प्रस्तुत करनी चाहिए । स्कॉट मेयर्स ने यह नाम दिया और आजकल उन्हें अक्सर अग्रेषण संदर्भ कहा जाता है। मूल रूप से, जब आप इस तरह से कुछ देखते हैं:

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

इस बात को ध्यान में रखें कि paramकोई रेवले संदर्भ नहीं है (जैसा कि किसी को निष्कर्ष निकालने के लिए लुभाया जा सकता है), लेकिन एक सार्वभौमिक संदर्भ *। सार्वभौमिक संदर्भों की विशेषता बहुत सीमित रूप से होती है (बस T&&, कांस्टेबल या समान योग्यताधारी के बिना) और प्रकार कटौती के द्वारा - प्रकार 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 जो चाहें कर सकते हैं (जैसे इसे स्थानांतरित करें)।

दूसरे मामले के लिए एक उदाहरण:

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

यह फ़ंक्शन टेम्प्लेट propरिटर्न मान में स्थानांतरित होना चाहिए अगर यह एक प्रतिद्वंद्विता है और इसे कॉपी करें यदि यह एक अंतराल है। इस मामले में कि हम std::forwardअंत में लोप हो गए हैं , हम हमेशा एक प्रति बनाएंगे, जो कि अधिक महंगी होती propहै जब एक प्रतिद्वंद्विता होती है।

* पूरी तरह से सटीक होने के लिए, एक सार्वभौमिक संदर्भ एक cv-unqualified टेम्पलेट पैरामीटर के संदर्भ में संदर्भ लेने की एक अवधारणा है।


0

क्या यह उदाहरण मदद करता है? मैंने std :: फॉरवर्ड का एक उपयोगी गैर जेनेरिक उदाहरण खोजने के लिए संघर्ष किया, लेकिन एक बैंक खाते के उदाहरण पर प्रहार किया कि हम एक तर्क के रूप में जमा की जाने वाली नकदी के साथ गुजरते हैं।

इसलिए अगर हमारे पास किसी खाते का एक कॉन्स्टेबल संस्करण है, तो हमें उम्मीद करनी चाहिए कि जब हम इसे हमारे जमा खाके के पास भेज दें तो <> कि कास्ट फंक्शन कहा जाता है; और फिर यह एक अपवाद फेंकता है (यह जा रहा विचार एक बंद खाता था!)

यदि हमारे पास एक नॉन कास्ट खाता है तो हमें खाते को संशोधित करने में सक्षम होना चाहिए।

#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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.