क्या मुझे एक std पास करना चाहिए :: const-reference द्वारा फ़ंक्शन?


141

मान लीजिए कि मेरे पास एक फ़ंक्शन है जो एक लेता है std::function:

void callFunction(std::function<void()> x)
{
    x();
}

क्या मुझे xइसके बजाय कॉन्स्ट-रेफरेंस पास करना चाहिए ? "

void callFunction(const std::function<void()>& x)
{
    x();
}

क्या इस सवाल का जवाब इस बात पर निर्भर करता है कि फ़ंक्शन इसके साथ क्या करता है? उदाहरण के लिए यदि यह एक क्लास मेंबर फंक्शन या कंस्ट्रक्टर है जो std::functionमेंबर वेरिएबल में स्टोर या इनिशियलाइज़ करता है ।


1
शायद ऩही। मुझे यकीन नहीं है, लेकिन मुझे उम्मीद sizeof(std::function)है कि इससे अधिक नहीं होगा 2 * sizeof(size_t), जो कि कम से कम आकार है जिसे आप कभी भी एक संदर्भ के लिए विचार करेंगे।
मैट पीटरसन

12
@ मैट: मुझे नहीं लगता कि std::functionरैपर का आकार उतना ही महत्वपूर्ण है जितना कि इसे कॉपी करने की जटिलता। यदि गहरी प्रतियां शामिल हैं, तो यह sizeofसुझाव से कहीं अधिक महंगा हो सकता है।
बेन वोइगट

आप moveमें कार्य करना चाहिए ?
यक्क - एडम नेवरामॉन्ट

operator()()है constतो एक स्थिरांक संदर्भ काम करना चाहिए। लेकिन मैंने कभी भी std :: function का उपयोग नहीं किया है।
नील बसु

@ यक मैं सीधे समारोह में एक लंबो पास करता हूं।
स्वेन एड्रिंग

जवाबों:


79

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

मान लीजिए कि आपके पास एक फ़ंक्शन है जिसे "UI थ्रेड में चलाएं"।

std::future<void> run_in_ui_thread( std::function<void()> )

जो "ui" धागे में कुछ कोड चलाता है, तब किए जाने पर संकेत देता futureहै। (यूआई फ्रेमवर्क में उपयोगी है जहां यूआई धागा वह है जहां आप यूआई तत्वों के साथ गड़बड़ करने वाले हैं)

हमारे पास दो हस्ताक्षर हैं जिन पर हम विचार कर रहे हैं:

std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)

अब, हम निम्नानुसार इनका उपयोग करने की संभावना रखते हैं:

run_in_ui_thread( [=]{
  // code goes here
} ).wait();

जो एक अनाम क्लोजर (एक लैम्ब्डा) बनाएगा, std::functionउसमें से एक का निर्माण करेगा, इसे run_in_ui_threadफंक्शन में पास करेगा , फिर मुख्य धागे में रनिंग खत्म होने का इंतजार करेगा।

मामले में (ए), std::functionसीधे हमारे मेमने से बनाया गया है, जो तब उपयोग किया जाता है run_in_ui_thread। लैम्ब्डा में moveघ है std::function, इसलिए किसी भी चल राज्य को कुशलता से इसमें ले जाया जाता है।

दूसरे मामले में, एक अस्थायी std::functionबनाया जाता है, लैम्ब्डा को इसमें moveघ में रखा जाता है, फिर उस अस्थायी std::functionका उपयोग के भीतर संदर्भ द्वारा किया जाता है run_in_ui_thread

अब तक, बहुत अच्छा - उनमें से दो पहचान के रूप में प्रदर्शन करते हैं। सिवाय run_in_ui_threadइसके समारोह तर्क की एक प्रति बनाने के लिए ui थ्रेड को निष्पादित करने के लिए भेजने के लिए जा रहा है! (इससे पहले कि यह इसके साथ किया जाता है, वापस आ जाएगा, इसलिए यह सिर्फ एक संदर्भ का उपयोग नहीं कर सकता है)। मामले (ए) के लिए, हम बस अपनी लंबी अवधि के भंडारण में। मामले में (बी), हम नकल करने के लिए मजबूर हैं ।movestd::functionstd::function

वह दुकान मूल्य से अधिक इष्टतम होकर गुजरती है। यदि कोई संभावना है कि आप की एक प्रति संग्रहीत कर रहे हैं std::function, तो मूल्य से पास करें। अन्यथा, किसी भी तरह से लगभग बराबर है: यदि आप एक ही भारी ले जा रहे हैं std::functionऔर एक के बाद एक उप विधि का उपयोग कर रहे हैं तो उप -मूल्य के लिए केवल नकारात्मक है। यह कहते हुए कि, एक के moveरूप में कुशल हो जाएगा const&

अब, दोनों के बीच कुछ अन्य अंतर हैं जो ज्यादातर किक करते हैं यदि हमारे पास लगातार स्थिति है std::function

मान लें कि std::functionस्टोर कुछ ऑब्जेक्ट के साथ है operator() const, लेकिन इसमें कुछ mutableडेटा सदस्य भी हैं जो इसे संशोधित करता है (कैसे अशिष्ट!)।

में std::function<> const&मामला है, mutableसंशोधित डेटा सदस्यों समारोह कॉल से बाहर का प्रचार करेंगे। में std::function<>मामला है, वे नहीं होगा।

यह एक अपेक्षाकृत अजीब कोने का मामला है।

आप चाहते हैं std::functionकि आप किसी अन्य के साथ संभवतः भारी वजन, सस्ते चल प्रकार का इलाज करें। चलना सस्ता है, नकल करना महंगा हो सकता है।


जैसा कि आप कहते हैं, "यदि आप इसे संग्रहीत कर रहे हैं, तो मान के पास" का सिमेंटिक लाभ यह है कि अनुबंध द्वारा फ़ंक्शन तर्क के पते को पारित नहीं कर सकता है। लेकिन क्या यह सच है कि "बैरिंग, एक कदम एक कास्ट के रूप में कुशल होगा"? मैं हमेशा कॉपी ऑपरेशन की लागत और चाल ऑपरेशन की लागत देखता हूं। const&मैं पास होने के साथ केवल कॉपी ऑपरेशन की लागत देखता हूं।
17:59 बजे से सेत्स्कोको

2
@ceztko दोनों (ए) और (बी) मामलों में, अस्थायी लंबोदा std::functionसे बनाया गया है। (ए) में, अस्थायी को तर्क में बदल दिया गया है run_in_ui_thread। (बी) में कहा गया है कि अस्थायी का संदर्भ दिया गया है run_in_ui_thread। जब तक आपके std::functions लंबोदर से अस्थायी के रूप में बनाए जाते हैं, तब तक यह खंड धारण करता है। पिछला पैराग्राफ उस मामले से संबंधित है जहां std::functionबनी रहती है। यदि हम भंडारण नहीं कर रहे हैं , तो बस एक मेमने से बना रहे हैं , function const&और functionठीक उसी ओवरहेड है।
यक्क - एडम नेवरामॉन्ट

ओह समझा! यह निश्चित रूप से इस बात पर निर्भर करता है कि बाहर क्या होता है run_in_ui_thread()। क्या "केवल संदर्भ द्वारा पास, लेकिन मैं पते को संग्रहीत नहीं करूंगा" कहने के लिए एक हस्ताक्षर है?
Ceztko

@ceztko नहीं, वहाँ नहीं है।
यक्क - एडम नेवरामोंट

1
@ यक-आदमनेवुमोंट यदि प्रतिद्वंद्विता रेफ द्वारा पारित करने के लिए एक और विकल्प को कवर करने के लिए और अधिक पूर्ण होगा:std::future<void> run_in_ui_thread( std::function<void()>&& )
Pavel P

33

यदि आप प्रदर्शन के बारे में चिंतित हैं, और आप वर्चुअल सदस्य फ़ंक्शन को परिभाषित नहीं कर रहे हैं, तो आप सबसे अधिक संभावना है कि इसका उपयोग न करें std::function

फ़नकार प्रकार बनाना एक टेम्प्लेट पैरामीटर std::functionफ़ाइबर तर्क को सम्मिलित करने की तुलना में अधिक अनुकूलन की अनुमति देता है। इन अनुकूलन के प्रभाव को कॉपी-बनाम-अप्रत्यक्ष चिंताओं को पारित करने के तरीके से बहुत अधिक प्रभावित होने की संभावना है std::function

और तेज:

template<typename Functor>
void callFunction(Functor&& x)
{
    x();
}

1
मैं वास्तव में प्रदर्शन के बारे में बिल्कुल भी चिंतित नहीं हूं। मैंने सिर्फ कॉन्स्ट-रेफरेंस का उपयोग करने के बारे में सोचा, जहां उनका उपयोग किया जाना आम बात है (स्ट्रिंग्स और वैक्टर दिमाग में आते हैं)।
स्वेन एड्रिंग

13
@ बान: मुझे लगता है कि इसे लागू करने का सबसे आधुनिक हिप्पी-फ्रेंडली तरीका है std::forward<Functor>(x)();, इसका उपयोग करने के लिए , फ़नकार के मूल्य श्रेणी को संरक्षित करना, क्योंकि यह एक "सार्वभौमिक" संदर्भ है। हालांकि 99% मामलों में फर्क नहीं पड़ रहा है।
GManNickG

1
@ आपके मामले के लिए ऐसा नहीं है, क्या मैं फ़ंक्शन को एक चाल के साथ कहूंगा? callFunction(std::move(myFunctor));
arias_JC

2
@arias_JC: यदि पैरामीटर एक लैम्ब्डा है, तो यह पहले से ही एक प्रतिद्वंद्विता है। यदि आपके पास एक अंतराल है, तो आप या तो उपयोग कर सकते हैं std::moveयदि आपको अब किसी अन्य तरीके से इसकी आवश्यकता नहीं होगी, या यदि आप मौजूदा ऑब्जेक्ट से बाहर नहीं जाना चाहते हैं तो सीधे पास करें। संदर्भ-पतन के नियम सुनिश्चित करते हैं कि callFunction<T&>()प्रकार का पैरामीटर है T&, नहीं T&&
बेन वोइगट

1
@BoltzmannBrain: मैंने उस बदलाव को नहीं करने के लिए चुना है क्योंकि यह केवल सबसे सरल मामले के लिए मान्य है, जब फ़ंक्शन केवल एक बार कहा जाता है। मेरा जवाब है कि "मुझे एक फ़ंक्शन ऑब्जेक्ट कैसे पास करना चाहिए?" और एक फ़ंक्शन तक सीमित नहीं है जो बिना किसी शर्त के कुछ भी नहीं करता है, उस फ़नकार को बिल्कुल एक बार आमंत्रित करें।
बेन वोइग्ट

25

C ++ 11 में हमेशा की तरह, मान / संदर्भ / कॉन्स्टेंस-रेफरेंस से गुजरना इस बात पर निर्भर करता है कि आप अपने तर्क के साथ क्या करते हैं। std::functionकोई अलग नहीं है।

मूल्य द्वारा पास करना आपको तर्क को एक चर में स्थानांतरित करने की अनुमति देता है (आमतौर पर एक वर्ग का सदस्य चर):

struct Foo {
    Foo(Object o) : m_o(std::move(o)) {}

    Object m_o;
};

जब आप जानते हैं कि आपका फ़ंक्शन अपने तर्क को स्थानांतरित कर देगा, तो यह सबसे अच्छा समाधान है, इस तरह से आपके उपयोगकर्ता यह नियंत्रित कर सकते हैं कि वे आपके फ़ंक्शन को कैसे कॉल करें:

Foo f1{Object()};               // move the temporary, followed by a move in the constructor
Foo f2{some_object};            // copy the object, followed by a move in the constructor
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor

मेरा मानना ​​है कि आप पहले से ही गैर (गैर) कॉन्स्टिट्यूशन के शब्दार्थों को जानते हैं, इसलिए मुझे इस बात पर विश्वास नहीं होगा। यदि आपको इस बारे में अधिक स्पष्टीकरण जोड़ने की आवश्यकता है, तो बस पूछें और मैं अपडेट करूंगा।

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