Std का उपयोग करने के मुख्य उद्देश्य क्या हैं :: आगे और यह किन समस्याओं का समाधान करता है?


438

सही अग्रेषण में, std::forwardनामांकित संदर्भों को नामांकित करने t1और नामांकित संदर्भों को नामांकित करने के लिए उपयोग किया जाता है t2। ऐसा करने का उद्देश्य क्या है? innerअगर हम छोड़ते हैं t1और t2lvalues ​​के रूप में कहा जाता है तो यह कैसे प्रभावित करेगा ?

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

जवाबों:


789

आपको अग्रेषण समस्या को समझना होगा। आप पूरी समस्या को विस्तार से पढ़ सकते हैं , लेकिन मैं संक्षेप में बताऊंगा।

मूल रूप से, अभिव्यक्ति को देखते हुए E(a, b, ... , c), हम चाहते हैं कि अभिव्यक्ति f(a, b, ... , c)समतुल्य हो। C ++ 03 में, यह असंभव है। कई प्रयास हैं, लेकिन वे सभी किसी न किसी संबंध में विफल हैं।


सबसे सरल है एक अंतराल-संदर्भ का उपयोग करना:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

लेकिन यह अस्थायी मानों को संभालने में विफल रहता है: f(1, 2, 3);क्योंकि वे एक अंतराल-संदर्भ के लिए बाध्य नहीं हो सकते हैं।

अगला प्रयास हो सकता है:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

जो उपरोक्त समस्या को ठीक करता है, लेकिन फ्लॉप हो जाता है। यह अब Eनॉन-कास्ट तर्क देने की अनुमति देने में विफल है :

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these

तीसरा प्रयास कॉन्स्टेंस-रेफरेंस को स्वीकार करता है, लेकिन फिर const_castवह constदूर है:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

यह सभी मूल्यों को स्वीकार करता है, सभी मूल्यों को पारित कर सकता है, लेकिन संभावित रूप से अपरिभाषित व्यवहार की ओर जाता है:

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!

एक अंतिम समाधान सब कुछ सही ढंग से संभालता है ... बनाए रखने की असंभवता की कीमत पर। आप कब्ज और गैर-कास्ट के सभी संयोजनों के fसाथ, ओवरलोड प्रदान करते हैं :

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

एन तर्कों को 2 एन संयोजन, एक दुःस्वप्न की आवश्यकता होती है । हम यह स्वचालित रूप से करना चाहते हैं।

(यह प्रभावी रूप से हमें C ++ 11 में हमारे लिए करने के लिए कंपाइलर है।)


C ++ 11 में, हमें इसे ठीक करने का मौका मिलता है। एक समाधान मौजूदा प्रकारों पर टेम्पलेट कटौती नियमों को संशोधित करता है, लेकिन यह संभावित रूप से कोड का एक बड़ा हिस्सा तोड़ देता है। इसलिए हमें दूसरा रास्ता खोजना होगा।

समाधान इसके बजाय नए जोड़े गए प्रतिद्वंद्वियों-संदर्भों का उपयोग करना है ; जब हम रेवल्यू-रेफरेंस प्रकारों को घटाते हैं और कोई वांछित परिणाम बनाते हैं, तो हम नए नियम पेश कर सकते हैं। आखिरकार, हम अब कोड को संभवतः नहीं तोड़ सकते।

यदि एक संदर्भ के संदर्भ में दिया जाता है (नोट संदर्भ एक अर्थपूर्ण शब्द है जिसका अर्थ दोनों है T&और T&&), हम परिणाम के प्रकार का पता लगाने के लिए निम्नलिखित नियम का उपयोग करते हैं:

"[दिया गया] एक टाइप टीआर जो कि टाइप टी का एक संदर्भ है, टाइप करने का प्रयास" सीवी टीआर के लिए लवल्यू संदर्भ "टाइप बनाता है" टी के लिए लैवल्यू संदर्भ ", जबकि टाइप बनाने का प्रयास" रेवल्यू रेफरेंस " cv TR "TR प्रकार बनाता है।"

या सारणीबद्ध रूप में:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

अगला, टेम्पलेट लॉजिक डिडक्शन के साथ: यदि कोई तर्क एक लैवल्यू A है, तो हम ए को लॉवल्यू रेफरेंस के साथ टेम्प्लेट तर्क की आपूर्ति करते हैं। अन्यथा, हम सामान्य रूप से कटौती करते हैं। यह तथाकथित सार्वभौमिक संदर्भ देता है (शब्द अग्रेषण संदर्भ अब आधिकारिक है)।

यह क्यों उपयोगी है? क्योंकि संयुक्त हम एक प्रकार के मूल्य श्रेणी का ट्रैक रखने की क्षमता रखते हैं: यदि यह एक अंतराल था, तो हमारे पास एक अंतराल-संदर्भ पैरामीटर है, अन्यथा हमारे पास एक अंतराल-संदर्भ पैरामीटर है।

कोड में:

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

आखिरी चीज चर के मूल्य श्रेणी को "आगे" करना है। ध्यान रखें, एक बार फ़ंक्शन के अंदर पैरामीटर को किसी भी चीज के लिए एक अंतराल के रूप में पारित किया जा सकता है:

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

यह अच्छा नहीं है। E को उसी प्रकार का मूल्य-श्रेणी प्राप्त करने की आवश्यकता है जो हमें मिला है! समाधान यह है:

static_cast<T&&>(x);

यह क्या करता है? विचार करें कि हम deduceफ़ंक्शन के अंदर हैं , और हमें एक अंतराल दिया गया है। इसका मतलब है Tकि एक है A&, और इसलिए स्थिर कलाकारों के लिए लक्ष्य प्रकार है A& &&, या बस A&। चूंकि xपहले से ही एक है A&, हम कुछ भी नहीं करते हैं और एक अंतराल संदर्भ के साथ छोड़ दिया जाता है।

जब हम एक प्रतिद्वंद्विता पारित किया गया Tहै A, तो स्थिर कलाकारों के लिए लक्ष्य प्रकार है A&&। कास्ट एक परिणामी अभिव्यक्ति का परिणाम है, जिसे अब एक अंतराल संदर्भ में पारित नहीं किया जा सकता है । हमने पैरामीटर का मान श्रेणी बनाए रखा है।

इन्हें एक साथ रखने से हमें "पूर्ण अग्रेषण" मिलता है:

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

जब fएक लैवल्यू प्राप्त होता है, एक लैवल्यू प्राप्त करता है E। जब fएक प्रतिद्वंद्विता प्राप्त करता है, एक प्रतिद्वंद्विता प्राप्त करता है E। उत्तम।


और निश्चित रूप से, हम बदसूरत से छुटकारा चाहते हैं। static_cast<T&&>याद रखने में अजीब और अजीब है; बजाय इसके कि यूटिलिटी फंक्शन बनाया जाए forward, जो समान कार्य करता है:

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);

1
fएक समारोह नहीं होगा , और एक अभिव्यक्ति नहीं होगी?
माइकल फौकरीस

1
समस्या कथन के संबंध में आपका अंतिम प्रयास सही नहीं है: यह कॉन्स्टेबल मानों को नॉन-कॉन्स्टैड के रूप में अग्रेषित करेगा, इस प्रकार यह बिल्कुल आगे नहीं बढ़ेगा। यह भी ध्यान दें कि पहले प्रयास में, const int iस्वीकार किया जाएगा: के Aलिए कटौती की जाती है const int। असफलताएं शाब्दिक अर्थों के लिए हैं। यह भी ध्यान दें कि कॉल के लिए deduced(1), x है int&&, नहीं int(सही अग्रेषण कभी भी प्रतिलिपि नहीं बनाता है, जैसा कि यदि xएक उप-मूल्य पैरामीटर होगा तो किया जाएगा)। मेरली Tहै int। इसका कारण यह है कि xफारवर्डर में एक लैवल्यू का मूल्यांकन किया जाता है, क्योंकि नामित रेवल्यू संदर्भ लैवल्यू एक्सप्रेशन बन जाते हैं।
जोहान्स शाउब -

5
क्या उपयोग करने में forwardया moveयहाँ कोई अंतर है? या यह सिर्फ एक शब्दार्थ अंतर है?
0x499602D2

28
@ डेविड: std::moveको स्पष्ट टेम्प्लेट तर्कों के बिना बुलाया जाना चाहिए और हमेशा एक परिणाम में होना चाहिए, जबकि std::forwardया तो समाप्त हो सकता है। उपयोग करें std::moveजब आप जानते हैं कि आपको अब मूल्य की आवश्यकता नहीं है और इसे कहीं और स्थानांतरित करना चाहते हैं, std::forwardतो अपने फ़ंक्शन टेम्पलेट में दिए गए मानों के अनुसार उपयोग करें।
GManNickG

5
पहले ठोस उदाहरणों के साथ शुरू करने और समस्या को प्रेरित करने के लिए धन्यवाद; बहुत मददगार!
श्रीवत्सआर

61

मुझे लगता है कि एक वैचारिक कोड लागू करना std :: फॉरवर्ड चर्चा में जोड़ सकता है। यह स्कॉट मेयर्स से एक स्लाइड है एक प्रभावी C ++ 11/14 सैम्पलर

वैचारिक कोड लागू करने वाला std :: आगे

समारोह moveकोड में है std::move। उस वार्ता में इसके लिए पहले (कार्य) कार्यान्वयन है। मुझे फ़ाइल st.h में libstdc ++ में आगे :: std का वास्तविक कार्यान्वयन मिला , लेकिन यह बिल्कुल भी नहीं है।

एक उपयोगकर्ता के दृष्टिकोण से, इसका अर्थ यह है कि std::forwardयह एक सशर्त डाली है। यह उपयोगी हो सकता है यदि मैं एक फ़ंक्शन लिख रहा हूं जो एक पैरामीटर में या तो एक अंतराल या अंतराल की अपेक्षा करता है और इसे किसी अन्य फ़ंक्शन के लिए एक प्रतिद्वंद्विता के रूप में पारित करना चाहता है, केवल अगर यह एक प्रतिद्वंद्विता के रूप में पारित किया गया था। अगर मैं पैरामीटर को std में नहीं लपेटता है :: आगे, यह हमेशा एक सामान्य संदर्भ के रूप में पारित किया जाएगा।

#include <iostream>
#include <string>
#include <utility>

void overloaded_function(std::string& param) {
  std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
  std::cout << "std::string&& version" << std::endl;
}

template<typename T>
void pass_through(T&& param) {
  overloaded_function(std::forward<T>(param));
}

int main() {
  std::string pes;
  pass_through(pes);
  pass_through(std::move(pes));
}

यकीन है कि यह प्रिंट करता है

std::string& version
std::string&& version

कोड पहले बताई गई बात से एक उदाहरण पर आधारित है। स्लाइड 10, शुरुआत से लगभग 15:00 बजे।


2
आपका दूसरा लिंक कहीं पूरी तरह से अलग होने की ओर इशारा करता है।
फ्राॅप

34

परफेक्ट फ़ॉरवर्डिंग में, std :: फॉरवर्ड का उपयोग नामी रेवल्यू रेफरेंस t1 और t2 को अनलॉवेल रेवल्यू रेफरेंस में बदलने के लिए किया जाता है। ऐसा करने का उद्देश्य क्या है? अगर हम t1 & t2 को लैवल्यू के रूप में छोड़ते हैं, तो यह फंक्शन इनहेरर फंक्शन को कैसे प्रभावित करेगा?

template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

यदि आप एक अभिव्यक्ति में नामांकित संदर्भ का उपयोग करते हैं तो यह वास्तव में एक लवल्यू है (क्योंकि आप नाम से ऑब्जेक्ट का उल्लेख करते हैं)। निम्नलिखित उदाहरण पर विचार करें:

void inner(int &,  int &);  // #1
void inner(int &&, int &&); // #2

अब, अगर हम outerइस तरह कहते हैं

outer(17,29);

हम चाहेंगे कि 17 और 29 को # 2 को अग्रेषित किया जाए क्योंकि 17 और 29 पूर्णांक शाब्दिक हैं और इस तरह के अंतराल हैं। लेकिन चूंकि t1और t2अभिव्यक्ति inner(t1,t2);में अंतराल हैं, आप # 2 के बजाय # 1 को लागू करेंगे। इसलिए हमें संदर्भों को फिर से अनाम संदर्भों में बदलने की आवश्यकता है std::forward। तो, t1में outerहमेशा एक lvalue अभिव्यक्ति है, जबकि forward<T1>(t1)एक rvalue अभिव्यक्ति के आधार पर हो सकता है T1। उत्तरार्द्ध केवल एक लवल्यू अभिव्यक्ति है अगर T1एक लैवल्यू संदर्भ है। और T1केवल एक लॉवेल्यू संदर्भ होने के लिए कटौती की जाती है, जब बाहरी के लिए पहला तर्क एक लैवल्यू अभिव्यक्ति था।


यह एक तरह से पानी के नीचे की व्याख्या है, लेकिन बहुत अच्छी तरह से किया गया और कार्यात्मक विवरण है। लोगों को इस उत्तर को पहले पढ़ना चाहिए और फिर गहराई से जाना चाहिए यदि वांछित हो
NicoBerrogorry

@sellibitze एक और प्रश्न, कौन सा कथन सही है जब int a; f (a): "चूंकि a एक लवल्यू है, इसलिए int (T &&) int (int & &&) के बराबर" या T && को int & के समान बनाना तो T को int & ”होना चाहिए? मैं बाद वाले को पसंद करता हूं।
जॉन

11

अगर हम t1 & t2 को लैवल्यू के रूप में छोड़ते हैं, तो यह कहा जाता है कि इनर फ़ंक्शन को कैसे प्रभावित करेगा?

यदि, तुरंत करने के बाद, T1प्रकार का है char, और T2एक वर्ग का है, तो आप t1प्रति और t2प्रति constसंदर्भ में पास करना चाहते हैं । ठीक है, जब तक inner()कि उन्हें गैर- constसंदर्भ के अनुसार नहीं लिया जाता है , अर्थात, जिस स्थिति में आप ऐसा करना चाहते हैं, वह भी।

outer()कार्यों के एक सेट को लिखने का प्रयास करें जो इसे बिना संदर्भ के संदर्भ में लागू करते हैं, तर्क के inner()प्रकार से पारित करने का सही तरीका समर्पित करते हैं । मुझे लगता है कि आपको कुछ 2 ^ 2 की आवश्यकता होगी, तर्कों को कम करने के लिए सुंदर भारी टेम्पलेट-मेटा सामान, और सभी मामलों के लिए यह अधिकार प्राप्त करने के लिए बहुत समय।

और फिर किसी व्यक्ति के साथ आता है inner()जो प्रति सूचक तर्क लेता है। मुझे लगता है कि अब 3 ^ 2 बनाता है। (या 4 ^ 2। नर्क, मुझे यह सोचने की कोशिश करने की जहमत नहीं उठाई जा सकती कि क्या constपॉइंटर से फर्क पड़ेगा।)

और फिर कल्पना करें कि आप इसे पांच मापदंडों के लिए करना चाहते हैं। या सात।

अब आप जानते हैं कि "सही अग्रेषण" के साथ कुछ उज्ज्वल दिमाग क्यों आए: यह आपके लिए यह सब करने के लिए कंपाइलर बनाता है।


5

एक बिंदु जिसे क्रिस्टल स्पष्ट नहीं किया गया है, वह static_cast<T&&>है const T&ठीक से संभालना भी।
कार्यक्रम:

#include <iostream>

using namespace std;

void g(const int&)
{
    cout << "const int&\n";
}

void g(int&)
{
    cout << "int&\n";
}

void g(int&&)
{
    cout << "int&&\n";
}

template <typename T>
void f(T&& a)
{
    g(static_cast<T&&>(a));
}

int main()
{
    cout << "f(1)\n";
    f(1);
    int a = 2;
    cout << "f(a)\n";
    f(a);
    const int b = 3;
    cout << "f(const b)\n";
    f(b);
    cout << "f(a * b)\n";
    f(a * b);
}

पैदा करता है:

f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&

ध्यान दें कि 'f' को एक टेम्प्लेट फंक्शन होना चाहिए। अगर इसे सिर्फ 'void f (int && a)' के रूप में परिभाषित किया जाता है तो यह काम नहीं करता है।


अच्छी बात है, इसलिए स्थिर कलाकारों में T && भी संदर्भ ढहने के नियमों का पालन करता है, है ना?
बार्नी

3

यह इस बात पर जोर देने के लिए सार्थक हो सकता है कि अग्रगामी / सार्वभौमिक संदर्भ के साथ अग्रानुक्रम में एक बाहरी विधि के साथ प्रयोग किया जाना है। निम्नलिखित कथनों के रूप में स्वयं द्वारा आगे का उपयोग करने की अनुमति है, लेकिन भ्रम पैदा करने के अलावा कोई अच्छा नहीं है। मानक समिति इस तरह के लचीलेपन को निष्क्रिय करना चाह सकती है अन्यथा हम इसके बजाय सिर्फ static_cast का उपयोग क्यों नहीं करते हैं?

     std::forward<int>(1);
     std::forward<std::string>("Hello");

मेरी राय में, चाल और आगे डिजाइन पैटर्न हैं जो आर-मूल्य संदर्भ प्रकार पेश किए जाने के बाद प्राकृतिक परिणाम हैं। हमें किसी विधि का नाम नहीं देना चाहिए, जब तक कि गलत उपयोग की मनाही न हो, इसका सही तरीके से उपयोग किया जाता है।


मुझे नहीं लगता कि C ++ समिति को लगता है कि उन पर भाषा मुहावरों का "सही ढंग से" उपयोग करने के लिए है, और न ही यह परिभाषित करते हैं कि "सही" उपयोग क्या है (हालांकि वे निश्चित रूप से दिशानिर्देश दे सकते हैं)। उस अंत तक, जबकि किसी व्यक्ति के शिक्षक, बॉस और दोस्तों का कर्तव्य हो सकता है कि उन्हें एक या दूसरे तरीके से चलाने के लिए, मेरा मानना ​​है कि C ++ समिति (और इसलिए मानक) में यह कर्तव्य नहीं है।
सिरग्यू

हाँ, मैं अभी N2951 पढ़ता हूँ और मैं मानता हूँ कि मानक समिति को किसी फ़ंक्शन के उपयोग के बारे में अनावश्यक सीमाएँ जोड़ने का कोई दायित्व नहीं है। लेकिन इन दो फंक्शन टेम्प्लेट्स (चाल और आगे) के नाम वास्तव में थोड़ा भ्रमित करने वाले होते हैं, जो लाइब्रेरी फाइल या स्टैंडर्ड डॉक्यूमेंटेशन (23.2.5 फॉरवर्ड / मूव हेल्पर्स) में केवल उनकी परिभाषा को देखते हैं। मानक के उदाहरण निश्चित रूप से अवधारणा को समझने में मदद करते हैं, लेकिन चीजों को थोड़ा और अधिक स्पष्ट करने के लिए अधिक टिप्पणियों को जोड़ना उपयोगी हो सकता है।
कॉलिन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.