क्या यह छोरों के लिए C ++ 11 का एक ज्ञात नुकसान है?


89

आइए कल्पना करें कि हमारे पास कुछ सदस्य कार्यों के साथ 3 युगल रखने के लिए एक संरचना है:

struct Vector {
  double x, y, z;
  // ...
  Vector &negate() {
    x = -x; y = -y; z = -z;
    return *this;
  }
  Vector &normalize() {
     double s = 1./sqrt(x*x+y*y+z*z);
     x *= s; y *= s; z *= s;
     return *this;
  }
  // ...
};

यह सादगी के लिए थोड़ा विरोधाभास है, लेकिन मुझे यकीन है कि आप सहमत हैं कि समान कोड वहाँ है। उदाहरण के लिए, विधियाँ आपको आसानी से श्रृंखला की अनुमति देती हैं:

Vector v = ...;
v.normalize().negate();

या और भी:

Vector v = Vector{1., 2., 3.}.normalize().negate();

अब अगर हमने शुरू () और अंत () फ़ंक्शन प्रदान किए हैं, तो हम अपने वेक्टर का उपयोग लूप के लिए एक नई शैली में कर सकते हैं, 3 निर्देशांक x, y और z पर लूप के लिए कह सकते हैं (आप संदेह नहीं कर सकते हैं कि अधिक "उपयोगी" उदाहरणों का निर्माण करें) वेक्टर की जगह जैसे स्ट्रिंग):

Vector v = ...;
for (double x : v) { ... }

हम भी कर सकते हैं:

Vector v = ...;
for (double x : v.normalize().negate()) { ... }

और भी:

for (double x : Vector{1., 2., 3.}) { ... }

हालांकि, निम्नलिखित (यह मुझे लगता है) टूट गया है:

for (double x : Vector{1., 2., 3.}.normalize()) { ... }

हालांकि यह पिछले दो उपयोगों के तार्किक संयोजन की तरह लगता है, मुझे लगता है कि यह अंतिम उपयोग एक झूलने वाला संदर्भ बनाता है जबकि पिछले दो पूरी तरह से ठीक हैं।

  • क्या यह सही और व्यापक रूप से सराहा गया है?
  • उपरोक्त में से कौन सा भाग "बुरा" हिस्सा है, जिसे टाला जाना चाहिए?
  • क्या लूप के लिए श्रेणी-आधारित की परिभाषा को बदलकर भाषा में सुधार किया जाएगा, ताकि लूप की अवधि के लिए फॉर-एक्सप्रेशन में निर्मित अस्थायी मौजूद हों?

किसी कारण से मुझे पहले पूछे गए समान प्रश्न याद आते हैं, यह भूल गए कि इसे क्या कहा जाता है।
पब्बी

मैं इसे भाषा का दोष मानता हूं। टेम्पोररी का जीवन फॉर-लूप के पूरे शरीर तक विस्तारित नहीं है, लेकिन केवल लूप के सेटअप के लिए। यह केवल सीमा सिंटैक्स नहीं है जो ग्रस्त है, क्लासिक सिंटैक्स भी करता है। मेरी राय में इनिट स्टेटमेंट में अस्थायी लोगों का जीवन लूप के पूर्ण जीवन के लिए विस्तारित होना चाहिए।
eda-qa mort-ora-y

1
@ edA-qamort-ora-y: मैं इस बात से सहमत हूं कि यहां थोड़ी सी भाषा का दोष है, लेकिन मुझे लगता है कि यह विशेष रूप से तथ्य है कि आजीवन विस्तार निहित है जब भी आप किसी संदर्भ में अस्थायी रूप से बांधते हैं, लेकिन किसी में नहीं अन्य स्थिति - यह अस्थायी जीवनकाल की अंतर्निहित समस्या के लिए एक आधे-बेक्ड समाधान की तरह लगता है, हालांकि यह कहना नहीं है कि यह स्पष्ट है कि बेहतर समाधान क्या होगा। अस्थायी निर्माण करते समय शायद एक स्पष्ट 'जीवनकाल विस्तार' वाक्यविन्यास, जो इसे वर्तमान ब्लॉक के अंत तक बना देता है - आपको क्या लगता है?
ndkrempel

@ edA-qamort-ora-y: ... यह एक अस्थायी चीज़ को संदर्भ में बाँधने के समान है, लेकिन पाठक के लिए अधिक स्पष्ट होने का लाभ यह है कि 'आजीवन विस्तार' हो रहा है, इनलाइन (एक अभिव्यक्ति में) , बल्कि एक अलग घोषणा की आवश्यकता होती है), और आपको अस्थायी नाम देने की आवश्यकता नहीं है।
ndkrempel

जवाबों:


64

क्या यह सही और व्यापक रूप से सराहा गया है?

हां, आपकी चीजों की समझ सही है।

उपरोक्त में से कौन सा भाग "बुरा" हिस्सा है, जिसे टाला जाना चाहिए?

खराब हिस्सा एक फ़ंक्शन से लौटे अस्थायी पर एल-वैल्यू संदर्भ ले रहा है, और इसे आर-वैल्यू संदर्भ के लिए बाध्य कर रहा है। यह बस के रूप में बुरा है:

auto &&t = Vector{1., 2., 3.}.normalize();

अस्थायी Vector{1., 2., 3.}के जीवनकाल को बढ़ाया नहीं जा सकता क्योंकि संकलक को यह पता नहीं है कि normalizeसंदर्भ से वापसी मूल्य ।

क्या लूप के लिए श्रेणी-आधारित की परिभाषा को बदलकर भाषा में सुधार किया जाएगा, ताकि लूप की अवधि के लिए फॉर-एक्सप्रेशन में निर्मित अस्थायी मौजूद हों?

यह बहुत ही असंगत होगा कि C ++ कैसे काम करता है।

क्या यह लोगों के लिए अस्थायी गोथिक या अभिव्यक्ति के लिए विभिन्न आलसी-मूल्यांकन विधियों पर जंजीरदार अभिव्यक्तियों का उपयोग करके किए गए कुछ गोचरों को रोक देगा? हाँ। लेकिन इसके लिए विशेष-केस संकलक कोड की भी आवश्यकता होगी, साथ ही साथ यह भ्रमित भी होना चाहिए कि यह अन्य अभिव्यक्ति निर्माणों के साथ काम क्यों नहीं करता है ।

एक बहुत अधिक उचित समाधान संकलक को सूचित करने का कोई तरीका होगा कि किसी फ़ंक्शन का रिटर्न मान हमेशा के लिए एक संदर्भ होता है this, और इसलिए यदि वापसी मान अस्थायी-विस्तारित निर्माण के लिए बाध्य है, तो यह सही अस्थायी का विस्तार करेगा। हालांकि यह एक भाषा-स्तरीय समाधान है।

वर्तमान में (यदि कंपाइलर इसका समर्थन करता है), तो आप इसे बना सकते हैं ताकि अस्थायी पर normalize नहीं बुलाया जा सके:

struct Vector {
  double x, y, z;
  // ...
  Vector &normalize() & {
     double s = 1./sqrt(x*x+y*y+z*z);
     x *= s; y *= s; z *= s;
     return *this;
  }
  Vector &normalize() && = delete;
};

यह Vector{1., 2., 3.}.normalize()एक संकलित त्रुटि देगा, जबकि v.normalize()ठीक काम करेगा। जाहिर है आप इस तरह से सही काम नहीं कर पाएंगे:

Vector t = Vector{1., 2., 3.}.normalize();

लेकिन आप गलत काम भी नहीं कर पाएंगे।

वैकल्पिक रूप से, जैसा कि टिप्पणियों में सुझाया गया है, आप एक संदर्भ के बजाय मूल्य संदर्भ संस्करण लौटा सकते हैं:

struct Vector {
  double x, y, z;
  // ...
  Vector &normalize() & {
     double s = 1./sqrt(x*x+y*y+z*z);
     x *= s; y *= s; z *= s;
     return *this;
  }
  Vector normalize() && {
     Vector ret = *this;
     ret.normalize();
     return ret;
  }
};

यदि Vectorस्थानांतरित करने के लिए वास्तविक संसाधनों के साथ एक प्रकार था, तो आप Vector ret = std::move(*this);इसके बजाय उपयोग कर सकते हैं । नामित रिटर्न वैल्यू ऑप्टिमाइज़ेशन प्रदर्शन के मामले में इसे उचित रूप से इष्टतम बनाता है।


1
जो चीज इसे "गोच" के रूप में और अधिक बना सकती है वह यह है कि लूप के लिए नया सिंटैक्टिक रूप से इस तथ्य को छिपा रहा है कि संदर्भ बंधन कवर के तहत चल रहा है - यानी यह आपके "बस के रूप में बुरा" उदाहरणों की तुलना में बहुत कम धुंधला है। यही कारण है कि यह लूप के लिए नए के लिए अतिरिक्त आजीवन विस्तार नियम का सुझाव देने योग्य था।
ndkrempel

1
@ndkrempel: हाँ, लेकिन यदि आप इसे ठीक करने के लिए एक भाषा सुविधा का प्रस्ताव करने जा रहे हैं (और इसलिए कम से कम 2017 तक इंतजार करना होगा), तो मैं पसंद करूंगा यदि यह अधिक व्यापक था, कुछ ऐसा जो हर जगह अस्थायी विस्तार की समस्या को हल कर सकता है
निकोल बोल

3
+1। अंतिम दृष्टिकोण पर, बजाय deleteआप एक वैकल्पिक ऑपरेशन प्रदान कर सकते हैं, जो एक वापसी प्रदान करता है: Vector normalize() && { normalize(); return std::move(*this); }(मेरा मानना ​​है कि normalizeफ़ंक्शन के अंदर कॉल लैवेल्यू अधिभार को भेज देगा, लेकिन किसी को इसकी जांच करनी चाहिए :)
डेविड रोड्रिग्ज़ -

3
मैंने इस तरीके &/ &&योग्यता को कभी नहीं देखा है । क्या यह C ++ 11 से है या यह कुछ (शायद व्यापक रूप से) मालिकाना संकलक विस्तार है। संभावनाओं को रोकती है।
क्रिश्चियन राऊ

1
@ChristianRau: यह C ++ 11 के लिए नया है, और C ++ 03 "कॉन्स्टेबल" के अनुरूप और गैर-स्थिर सदस्य कार्यों की "अस्थिर" योग्यता है, जिसमें यह कुछ अर्थों में "यह" योग्य है। g ++ 4.7.0 हालांकि इसका समर्थन नहीं करता है।
ndkrempel

25

(डबल x: वेक्टर {1।, 2., 3।}। सामान्य करें ()) {...}

यह भाषा की सीमा नहीं है, बल्कि आपके कोड की समस्या है। अभिव्यक्ति Vector{1., 2., 3.}एक अस्थायी बनाता है, लेकिन normalizeफ़ंक्शन एक अंतराल-संदर्भ देता है । क्योंकि अभिव्यक्ति एक अंतराल है , संकलक मानता है कि वस्तु जीवित होगी, लेकिन क्योंकि यह एक अस्थायी का संदर्भ है, पूर्ण अभिव्यक्ति का मूल्यांकन होने के बाद वस्तु मर जाती है, इसलिए आपको झूलने वाले संदर्भ के साथ छोड़ दिया जाता है।

अब, यदि आप वर्तमान ऑब्जेक्ट के संदर्भ के बजाय मूल्य द्वारा एक नई वस्तु को वापस करने के लिए अपना डिज़ाइन बदलते हैं, तो कोई समस्या नहीं होगी और कोड अपेक्षित रूप से काम करेगा।


1
क्या constइस मामले में एक संदर्भ वस्तु के जीवनकाल का विस्तार करेगा?
डेविड स्टोन

5
जो normalize()एक मौजूदा वस्तु पर एक उत्परिवर्तन समारोह के रूप में स्पष्ट रूप से वांछित शब्दार्थ को तोड़ देगा । इस प्रकार प्रश्न। एक अस्थायी में एक "विस्तारित जीवनकाल" होता है, जब इसका उपयोग एक पुनरावृत्ति के विशिष्ट उद्देश्य के लिए किया जाता है, और अन्यथा नहीं, क्या मुझे लगता है कि एक भ्रमित मिसफिट्योर है।
एंडी रॉस

2
@AndyRoss: क्यों? आर-मान संदर्भ (या ) के लिए बाध्य कोई भी अस्थायी const&जीवनकाल बढ़ाया गया है।
निकोल बोलस

2
@ndkrempel: फिर भी, लूप के लिए रेंज-आधारित की सीमा नहीं है, यदि आप किसी संदर्भ में बाँधते हैं तो यही समस्या आएगी Vector & r = Vector{1.,2.,3.}.normalize();:। आपके डिज़ाइन में वह सीमा है, और इसका मतलब है कि या तो आप मूल्य से वापसी करने को तैयार हैं (जो कई परिस्थितियों में समझ में आ सकता है, और इसी तरह से अधिक -संदर्भ और चाल के साथ ), या फिर आपको समस्या को संभालने की आवश्यकता है कॉल: एक उचित चर बनाएँ, फिर इसे लूप के लिए उपयोग करें। यह भी ध्यान रखें कि अभिव्यक्ति Vector v = Vector{1., 2., 3.}.normalize().negate();बनाता दो वस्तुओं ...
dribeas - डेविड Rodríguez

1
@ DavidRodríguez-dribeas: बंधन टू कॉन्स्टेंस-रेफ़रेंस की समस्या यह है: T const& f(T const&);पूरी तरह से ठीक है। T const& t = f(T());पूरी तरह से ठीक है। और फिर, एक और टीयू में आपको पता चलता है T const& f(T const& t) { return t; }और आप रोते हैं ... यदि operator+मूल्यों पर काम करता है, तो यह सुरक्षित है ; तब कंपाइलर कॉपी आउट को ऑप्टिमाइज़ कर सकता है (स्पीड चाहते हैं? वैल्यूज़ द्वारा पास), लेकिन यह एक बोनस है। मैं जिन अस्थायी कर्मचारियों की अनुमति देता था, उन्हें केवल बाध्यकारी मानों के संदर्भों के लिए बाध्य किया जाता है, लेकिन फ़ंक्शंस को तब सुरक्षा के लिए मान लौटाए जाने चाहिए और कॉपी एलीसन / मूव सेमेंटिक्स पर भरोसा करना चाहिए।
मैथ्यू एम।

4

IMHO, दूसरा उदाहरण पहले से ही त्रुटिपूर्ण है। *thisआपके द्वारा बताए गए तरीके से संशोधन करने वाले परिचालक वापसी सुविधाजनक है: यह संशोधक का पीछा करने की अनुमति देता है। इसका उपयोग केवल संशोधन के परिणाम को सौंपने के लिए किया जा सकता है, लेकिन ऐसा करना त्रुटि-प्रवण है क्योंकि इसे आसानी से अनदेखा किया जा सकता है। अगर मुझे कुछ ऐसा दिख रहा है

Vector v{1., 2., 3.};
auto foo = somefunction1(v, 17);
auto bar = somefunction2(true, v, 2, foo);
auto baz = somefunction3(bar.quun(v), 93.2, v.qwarv(foo));

मुझे स्वचालित रूप से संदेह नहीं होगा कि फ़ंक्शन संशोधित हैं v साइड-इफेक्ट के रूप में । बेशक, वे कर सकते थे , लेकिन यह भ्रामक होगा। इसलिए अगर मुझे ऐसा कुछ लिखना था, तो मैं यह सुनिश्चित करूंगा कि vस्थिर रहे। आपके उदाहरण के लिए, मैं मुफ्त कार्य जोड़ूंगा

auto normalized(Vector v) -> Vector {return v.normalize();}
auto negated(Vector v) -> Vector {return v.negate();}

और फिर छोरों को लिखें

for( double x : negated(normalized(v)) ) { ... }

तथा

for( double x : normalized(Vector{1., 2., 3}) ) { ... }

यह IMO बेहतर पठनीय है, और यह अधिक सुरक्षित है। बेशक, इसके लिए एक अतिरिक्त प्रतिलिपि की आवश्यकता होती है, हालांकि ढेर-आवंटित डेटा के लिए यह संभव हो सकता है एक सस्ते C ++ 11 चालन ऑपरेशन में।


धन्यवाद। हमेशा की तरह, कई विकल्प हैं। एक स्थिति जहां आपका सुझाव व्यवहार्य नहीं हो सकता है, अगर वेक्टर 1000 डबल्स का एक सरणी (ढेर आवंटित नहीं) है, उदाहरण के लिए। दक्षता, आसानी से उपयोग और सुरक्षा के उपयोग का व्यापार बंद।
ndkrempel

2
हाँ, लेकिन यह आकार के साथ संरचनाओं के लिए शायद ही उपयोगी है> स्टैक पर on100, वैसे भी।
leftaroundabout
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.