क्या C ++ 11 में पास-बाय-वैल्यू एक उचित डिफॉल्ट है?


142

पारंपरिक सी ++ में, कार्यों और विधियों में मूल्य से गुजरना बड़ी वस्तुओं के लिए धीमा होता है, और आम तौर पर पर फेंक दिया जाता है। इसके बजाय, C ++ प्रोग्रामर आसपास के संदर्भों को पास करते हैं, जो तेज है, लेकिन जो स्वामित्व के चारों ओर और विशेष रूप से स्मृति प्रबंधन के आसपास सभी प्रकार के जटिल प्रश्नों का परिचय देता है (इस घटना में कि वस्तु ढेर-आवंटित है)

अब, C ++ 11 में, हमारे पास रेवल्यू रेफरेंस और मूव कंस्ट्रक्टर्स हैं, जिसका अर्थ है कि किसी बड़े ऑब्जेक्ट (जैसे std::vector) को लागू करना संभव है, जो किसी फ़ंक्शन के मान से और उसके बाहर जाने के लिए सस्ता है।

तो, क्या इसका मतलब यह है कि डिफ़ॉल्ट को इस तरह के std::vectorऔर जैसे उदाहरणों के लिए मूल्य से गुजरना चाहिए std::string? कस्टम ऑब्जेक्ट्स के बारे में क्या? नया सबसे अच्छा अभ्यास क्या है?


22
pass by reference ... which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated)। मुझे समझ में नहीं आता कि यह स्वामित्व के लिए जटिल या समस्याग्रस्त कैसे है? क्या मुझे कुछ याद आ सकता है?
Iammilind

1
@iammilind: व्यक्तिगत अनुभव से एक उदाहरण। एक धागे में एक स्ट्रिंग ऑब्जेक्ट है। यह एक ऐसे फ़ंक्शन को दिया जाता है जो किसी अन्य थ्रेड को जन्म देता है, लेकिन कॉल करने वाले के लिए अनजान फ़ंक्शन ने स्ट्रिंग को const std::string&कॉपी के रूप में नहीं लिया । पहला धागा फिर बाहर निकल गया ...
Zan Lynx

12
@ZanLynx: यह एक फ़ंक्शन की तरह लगता है जिसे स्पष्ट रूप से कभी भी थ्रेड फ़ंक्शन के रूप में बुलाया जाना नहीं था।
निकोल बोलस

5
Iammilind से सहमत, मुझे कोई समस्या नहीं दिख रही है। कॉन्स्ट रेफरेंस द्वारा पास करना "बड़ी" ऑब्जेक्ट्स के लिए आपका डिफ़ॉल्ट होना चाहिए, और छोटी वस्तुओं के लिए मूल्य के अनुसार। मैं लगभग 16 बाइट्स (या 32 बिट सिस्टम पर 4 पॉइंटर्स) पर बड़े और छोटे के बीच की सीमा लगाता हूँ।
जेएन

जवाबों:


138

यदि आपको शरीर के अंदर कॉपी बनाने की आवश्यकता है तो यह एक उचित डिफ़ॉल्ट है। यह वही है जो डेव अब्राहम वकालत कर रहा है :

दिशानिर्देश: अपने कार्य तर्कों की नकल न करें। इसके बजाय, उन्हें मान से पास करें और कंपाइलर को कॉपी करने दें।

कोड में इसका मतलब यह नहीं है:

void foo(T const& t)
{
    auto copy = t;
    // ...
}

लेकिन ऐसा करो:

void foo(T t)
{
    // ...
}

जिसका फायदा यह है कि कॉल करने वाला इसका उपयोग कर सकता fooहै:

T lval;
foo(lval); // copy from lvalue
foo(T {}); // (potential) move from prvalue
foo(std::move(lval)); // (potential) move from xvalue

और केवल न्यूनतम कार्य किया जाता है। आपको संदर्भों के साथ ऐसा करने के लिए दो अधिभार की आवश्यकता होगी, void foo(T const&);और void foo(T&&);

इसे ध्यान में रखते हुए, मैंने अब अपने मूल्यवान रचनाकारों को इस तरह लिखा है:

class T {
    U u;
    V v;
public:
    T(U u, V v)
        : u(std::move(u))
        , v(std::move(v))
    {}
};

अन्यथा, संदर्भ से गुजरना constअभी भी उचित है।


29
+1, विशेष रूप से अंतिम बिट के लिए :) किसी को यह नहीं भूलना चाहिए कि मूव कंस्ट्रक्टर्स को केवल तब ही इनवॉइस किया जा सकता है जब से ले जाने वाली वस्तु के अपरिवर्तित होने की उम्मीद नहीं है: SomeProperty p; for (auto x: vec) { x.foo(p); }उदाहरण के लिए, फिट नहीं है। इसके अलावा, मूव कंस्ट्रक्टर्स की लागत (जितनी बड़ी वस्तु होती है, उतनी ही अधिक) const&अनिवार्य रूप से मुफ्त होती है।
Matthieu M.

25
@MatthieuM। लेकिन यह जानना महत्वपूर्ण है कि "वस्तु जितनी बड़ी होती है, उतनी ही उच्च लागत" वास्तव में इसका अर्थ है: "बड़ा" वास्तव में इसका अर्थ है "अधिक सदस्य चर"। उदाहरण के लिए, std::vectorएक लाख तत्वों के साथ बढ़ना पांच तत्वों के साथ एक को स्थानांतरित करने के समान है क्योंकि केवल पॉइंटर पर सरणी के लिए सूचक को स्थानांतरित किया जाता है, न कि हर वस्तु वेक्टर में। तो यह वास्तव में एक मुद्दा नहीं है।
लुकास

+1 जब से मैंने C ++ 11 का उपयोग करना शुरू किया है तब से मैं पास-दर-मूल्य-चाल निर्माण का उपयोग करना चाहता हूं। यह मुझे कुछ असहज महसूस कर रहा है, क्योंकि मेरे कोड के बाद अब std::moveसभी जगह है ..
'14:01

1
इसके साथ एक जोखिम है const&, जिसने मुझे कुछ समय के लिए उलझा दिया है। void foo(const T&); int main() { S s; foo(s); }। यह संकलित कर सकता है, भले ही प्रकार अलग-अलग हों, यदि कोई T निर्माता है जो तर्क के रूप में S लेता है। यह धीमा हो सकता है, क्योंकि एक बड़ी टी ऑब्जेक्ट का निर्माण हो सकता है। आप सोच सकते हैं कि नकल के बिना आपका संदर्भ पारित हो, लेकिन आप हो सकते हैं। मेरे द्वारा पूछे गए एक प्रश्न का उत्तर देखें । मूल रूप से, &आमतौर पर केवल अंतराल के लिए बांधता है, लेकिन इसके लिए एक अपवाद है rvalue। विकल्प हैं।
हारून मैकडैड

1
@AaronMcDaid यह पुरानी खबर है, इस मायने में कि आपको हमेशा C ++ 11 से पहले भी जागरूक रहना होगा। और उस संबंध में बहुत कुछ नहीं बदला है।
ल्यूक डैंटन

71

लगभग सभी मामलों में, आपका शब्दार्थ या तो होना चाहिए:

bar(foo f); // want to obtain a copy of f
bar(const foo& f); // want to read f
bar(foo& f); // want to modify f

अन्य सभी हस्ताक्षरों का उपयोग केवल संयमपूर्वक और अच्छे औचित्य के साथ किया जाना चाहिए। कंपाइलर अब बहुत हमेशा इन सबसे कुशल तरीके से काम करेगा। आप बस अपना कोड लिखकर प्राप्त कर सकते हैं!


2
हालाँकि मैं एक पॉइंटर पास करना पसंद करता हूँ अगर मैं एक तर्क को संशोधित करने जा रहा हूँ। मैं Google शैली मार्गदर्शिका से सहमत हूं कि इससे यह और स्पष्ट हो जाता है कि फ़ंक्शन के हस्ताक्षर ( google-styleguide.googlecode.com/svn/trunk/… ) को दोबारा जांचने की आवश्यकता के बिना तर्क को संशोधित किया जाएगा ।
मैक्स लाइब्बर्ट

40
पासिंग पॉइंटर्स को नापसंद करने का कारण यह है कि यह मेरे कार्यों में एक संभावित विफलता स्थिति जोड़ता है। मैं अपने सभी कार्यों को लिखने की कोशिश करता हूं, ताकि वे काफी हद तक सही हों, क्योंकि यह कीड़े को छिपाने के लिए जगह को बहुत कम कर देता है, foo(bar& x) { x.a = 3; }एक बहुत अधिक विश्वसनीय (और पठनीय!) की एक foo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;
झोंपड़ी

22
@Max Lybbert: एक पॉइंटर पैरामीटर के साथ, आपको फ़ंक्शन के हस्ताक्षर की जांच करने की आवश्यकता नहीं है, लेकिन आपको यह जानने के लिए दस्तावेज़ीकरण की जांच करने की आवश्यकता है कि क्या आप अशक्त बिंदुओं को पारित करने की अनुमति देते हैं, यदि फ़ंक्शन स्वामित्व ले जाएगा, आदि IMHO, एक सूचक पैरामीटर एक गैर-कॉन्स्टेंस संदर्भ की तुलना में बहुत कम संकेत देता है। मैं इस बात से सहमत हूं कि कॉल साइट पर विज़ुअल क्लू होना अच्छा होगा कि तर्क को संशोधित किया जा सकता है (जैसे refC # में कीवर्ड)।
ल्यूक टॉरिल ने

मूल्य से गुजरने और चाल शब्दार्थों पर भरोसा करने के संबंध में, मुझे लगता है कि इन तीन विकल्पों को पैरामीटर के इच्छित उपयोग को समझाने का एक बेहतर काम करना है। ये ऐसे दिशानिर्देश हैं जिनका मैं हमेशा पालन करता हूं।
ट्रेवर हिक्की

1
@AaronMcDaid वे is shared_ptr intended to never be null? Much as (I think) unique_ptr is?दोनों धारणाएँ गलत हैं। unique_ptrऔर shared_ptrअशक्त / nullptrमान धारण कर सकते हैं । यदि आप अशक्त मूल्यों के बारे में चिंता नहीं करना चाहते हैं, तो आपको संदर्भों का उपयोग करना चाहिए, क्योंकि वे कभी भी अशक्त नहीं हो सकते। आपको टाइप भी नहीं करना पड़ेगा ->, जो आपको परेशान करने वाला लगता है :)
जूलियन

10

मान द्वारा मानदंड पास करें यदि फ़ंक्शन बॉडी के अंदर आपको ऑब्जेक्ट की प्रतिलिपि की आवश्यकता होती है या केवल ऑब्जेक्ट को स्थानांतरित करने की आवश्यकता होती है। const&यदि आपको केवल ऑब्जेक्ट तक नॉन-म्यूटिंग एक्सेस की आवश्यकता है तो पास करें ।

ऑब्जेक्ट कॉपी उदाहरण:

void copy_antipattern(T const& t) { // (Don't do this.)
    auto copy = t;
    t.some_mutating_function();
}

void copy_pattern(T t) { // (Do this instead.)
    t.some_mutating_function();
}

वस्तु चाल उदाहरण:

std::vector<T> v; 

void move_antipattern(T const& t) {
    v.push_back(t); 
}

void move_pattern(T t) {
    v.push_back(std::move(t)); 
}

गैर-उत्परिवर्तन पहुंच उदाहरण:

void read_pattern(T const& t) {
    t.some_const_function();
}

तर्क के लिए, डेव Abrahams और जियांग फैन द्वारा इन ब्लॉग पोस्ट देखें ।


0

किसी फ़ंक्शन के हस्ताक्षर को यह दर्शाया जाना चाहिए कि इसका उपयोग करना है। आशावादी के लिए भी पठनीयता महत्वपूर्ण है।

यह एक अनुकूलक के लिए सबसे तेज़ कोड बनाने के लिए सबसे अच्छा पूर्व शर्त है - सिद्धांत में कम से कम और यदि वास्तविकता में नहीं तो कुछ वर्षों में वास्तविकता।

पैरामीटर पासिंग के संदर्भ में प्रदर्शन के विचार अक्सर बहुत अधिक होते हैं। परफेक्ट फॉरवर्डिंग एक उदाहरण है। emplace_backवैसे भी फ़ंक्शंस ज्यादातर बहुत कम हैं और इनलेटेड हैं।

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