खोज…


कॉपी इलेक्शन का उद्देश्य

मानक में ऐसे स्थान होते हैं जहाँ किसी वस्तु को आरम्भ करने के लिए किसी वस्तु की नकल की जाती है या स्थानांतरित किया जाता है। कॉपी एलीशन (कभी-कभी रिटर्न वैल्यू ऑप्टिमाइजेशन कहा जाता है) एक अनुकूलन है जिसके तहत, कुछ विशिष्ट परिस्थितियों में, एक कंपाइलर को कॉपी से बचने या स्थानांतरित करने की अनुमति दी जाती है, भले ही मानक कहता है कि ऐसा होना चाहिए।

निम्नलिखित कार्य पर विचार करें:

std::string get_string()
{
  return std::string("I am a string.");
}

मानक के सख्त शब्दों के अनुसार, यह फ़ंक्शन एक अस्थायी std::string को इनिशियलाइज़ करेगा, फिर कॉपी / मूव करेगा जो रिटर्न वैल्यू ऑब्जेक्ट में जाएगा, फिर अस्थायी को नष्ट कर देगा। मानक बहुत स्पष्ट है कि इस तरह से कोड की व्याख्या की जाती है।

कॉपी एलिसेंस एक नियम है जो अस्थायी और इसके बाद की कॉपी / विनाश के निर्माण को अनदेखा करने के लिए C ++ कंपाइलर की अनुमति देता है। यही है, संकलक अस्थायी के लिए प्रारंभिक अभिव्यक्ति ले सकता है और सीधे इसे से फ़ंक्शन का रिटर्न मान प्रारंभ कर सकता है। यह स्पष्ट रूप से प्रदर्शन बचाता है।

हालांकि, उपयोगकर्ता पर इसके दो दृश्य प्रभाव हैं:

  1. टाइप में कॉपी / मूव कंस्ट्रक्टर होना चाहिए जिसे कॉल किया गया होगा। यहां तक कि अगर कंपाइलर कॉपी / चाल को समाप्त कर देता है, तो भी टाइप को कॉपी / स्थानांतरित करने में सक्षम होना चाहिए।

  2. उन परिस्थितियों में जहां नकल हो सकती है, कॉपी / मूव कंस्ट्रक्टर के साइड-इफेक्ट की गारंटी नहीं है। निम्नलिखित को धयान मे रखते हुए:

सी ++ 11
struct my_type
{
  my_type() = default;
  my_type(const my_type &) {std::cout <<"Copying\n";}
  my_type(my_type &&) {std::cout <<"Moving\n";}
};

my_type func()
{
  return my_type();
}

func कॉलिंग क्या करेगी? खैर, यह कभी भी "नकल" नहीं छापेगा, क्योंकि अस्थायी एक my_type है और my_type एक जंगम प्रकार है। तो क्या यह "मूविंग" प्रिंट करेगा?

कॉपी एलिजन नियम के बिना, हमेशा "मूविंग" प्रिंट करना आवश्यक होगा। लेकिन क्योंकि कॉपी एलिजन नियम मौजूद है, इसलिए मूव कंस्ट्रक्टर को कॉल किया जा सकता है या नहीं; यह कार्यान्वयन-निर्भर है।

और इसलिए, आप संदर्भों में कॉपी / मूव कंस्ट्रक्टर्स की कॉलिंग पर निर्भर नहीं हो सकते हैं, जहां एलक्शन संभव है।

चूँकि elision एक अनुकूलन है, इसलिए आपका कंपाइलर सभी मामलों में elision का समर्थन नहीं कर सकता है। और इस बात की परवाह किए बिना कि संकलक किसी विशेष मामले को समाप्त करता है या नहीं, फिर भी टाइप को ऑपरेशन के इलास्टेड होने का समर्थन करना चाहिए। इसलिए यदि एक कॉपी निर्माण को हटा दिया जाता है, तो टाइप में एक कॉपी कंस्ट्रक्टर होना चाहिए, भले ही इसे कॉल नहीं किया जाएगा।

गारंटी की नकल

सी ++ 17

आम तौर पर, एलिसेंस एक अनुकूलन है। जबकि वस्तुतः प्रत्येक संकलक समर्थन मामलों की सबसे सरलता में चींटियों की नकल करते हैं, जबकि अभी भी उपयोगकर्ताओं पर एक विशेष बोझ डाला जाता है। अर्थात्, जिस प्रकार की प्रतिलिपि / चाल को समाप्त किया जा रहा है, उसके पास अभी भी प्रतिलिपि / चाल कार्रवाई होनी चाहिए जो कि उत्तीर्ण हुई थी।

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

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  return std::lock_guard<std::mutex>(a_mutex);
}

यह उन मामलों में उपयोगी हो सकता है जहां a_mutex एक म्यूटेक्स है जो निजी तौर पर किसी प्रणाली द्वारा आयोजित किया जाता है, फिर भी एक बाहरी उपयोगकर्ता इसे स्कॉप्ड लॉक करना चाह सकता है।

यह भी कानूनी नहीं है, क्योंकि std::lock_guard को कॉपी या स्थानांतरित नहीं किया जा सकता है। हालांकि लगभग हर सी ++ संकलक कॉपी / चाल को खत्म कर देगा, फिर भी मानक को उस प्रकार के ऑपरेशन की आवश्यकता होती है।

C ++ 17 तक।

सी ++ 17 कुछ भावों के बहुत अर्थ को प्रभावी ढंग से पुनर्परिभाषित करने के द्वारा elision को अनिवार्य करता है ताकि कोई कॉपी / मूविंग न हो। उपरोक्त कोड पर विचार करें।

पूर्व-सी ++ 17 शब्दों के तहत, वह कोड एक अस्थायी बनाने के लिए कहता है और फिर अस्थायी का उपयोग कॉपी वैल्यू / रिटर्न मूल्य में स्थानांतरित करने के लिए करता है, लेकिन अस्थायी प्रतिलिपि को समाप्त किया जा सकता है। C ++ 17 शब्दांकन के तहत, यह बिल्कुल अस्थायी नहीं है।

सी ++ 17 में, किसी भी प्रचलित अभिव्यक्ति , जब अभिव्यक्ति के रूप में एक ही प्रकार के ऑब्जेक्ट को इनिशियलाइज़ करने के लिए उपयोग किया जाता है, तो एक अस्थायी उत्पन्न नहीं होता है। अभिव्यक्ति सीधे उस वस्तु को आरंभ करती है। यदि आप रिटर्न मान के रूप में उसी प्रकार का प्रचलन वापस करते हैं, तो टाइप को कॉपी / मूव कंस्ट्रक्टर की आवश्यकता नहीं है। और इसलिए, C ++ 17 नियमों के तहत, उपरोक्त कोड काम कर सकता है।

C ++ 17 शब्दांकन उन मामलों में काम करता है जहां प्रील्यूव्स का प्रकार प्रारंभिक होने के प्रकार से मेल खाता है। तो ऊपर दिए गए get_lock देखते get_lock , इसके लिए कॉपी / मूव की भी आवश्यकता नहीं होगी:

std::lock_guard the_lock = get_lock();

चूंकि get_lock का परिणाम एक प्रचलित अभिव्यक्ति है, जिसका उपयोग उसी प्रकार के ऑब्जेक्ट को इनिशियलाइज़ करने के लिए किया जा रहा है, कोई भी कॉपी या मूविंग नहीं होगा। वह अभिव्यक्ति कभी अस्थायी नहीं बनती; इसका उपयोग सीधे the_lock को आरंभ करने के लिए किया जाता है। कोई elision नहीं है क्योंकि elied elide होने के लिए कोई प्रतिलिपि / चाल नहीं है।

"गारंटीकृत कॉपी एलिसन" शब्द इसलिए एक मिथ्या नाम के कुछ है, लेकिन यह सुविधा का नाम है क्योंकि यह सी ++ 17 मानकीकरण के लिए प्रस्तावित है । यह बिल्कुल भी गारंटी की गारंटी नहीं देता है; यह प्रतिलिपि / चाल को पूरी तरह से समाप्त कर देता है, C ++ को फिर से परिभाषित करता है ताकि कभी भी नकल / चाल को समाप्त न किया जाए।

यह सुविधा केवल उन मामलों में काम करती है जो एक भाव अभिव्यक्ति से जुड़े होते हैं। इस प्रकार, यह सामान्य नियम नियमों का उपयोग करता है:

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  std::lock_guard<std::mutex> my_lock(a_mutex);
  //Do stuff
  return my_lock;
}

हालांकि यह नकल के लिए एक वैध मामला है, C ++ 17 नियम इस मामले में प्रतिलिपि / चाल को समाप्त नहीं करते हैं। इस प्रकार, रिटर्न मान को आरंभ करने के लिए उपयोग करने के लिए टाइप के पास अभी भी एक कॉपी / मूव कंस्ट्रक्टर होना चाहिए। और चूंकि lock_guard नहीं करता है, यह अभी भी एक संकलन त्रुटि है। कार्यान्वयन के लिए प्रतियों को पास करने से इनकार करने की अनुमति दी जाती है जब गुजरता है या तुच्छ-प्रतिलिपि प्रकार के ऑब्जेक्ट को वापस करता है। यह ऐसे ऑब्जेक्ट्स को रजिस्टरों में इधर-उधर जाने की अनुमति देने के लिए है, जिन्हें कुछ ABI अपने कॉलिंग कन्वेंशन में अनिवार्य कर सकते हैं।

struct trivially_copyable {
    int a;  
};

void foo (trivially_copyable a) {}

foo(trivially_copyable{}); //copy elision not mandated

मान एलीशन लौटाएं

यदि आप किसी फ़ंक्शन से प्रील्यूव एक्सप्रेशन लौटाते हैं , और प्रील्यूव एक्सप्रेशन में फ़ंक्शन के रिटर्न टाइप की तरह ही टाइप होता है, तो प्रिल्यू अस्थायी से कॉपी को एल्डर किया जा सकता है:

std::string func()
{
  return std::string("foo");
}

बहुत ज्यादा सभी कंपाइलर इस मामले में अस्थायी निर्माण को खत्म कर देंगे।

पैरामीटर elision

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

void func(std::string str) { ... }

func(std::string("foo"));

यह एक अस्थायी string बनाने के लिए कहता है, फिर इसे फंक्शन पैरामीटर str में स्थानांतरित करें। कॉपी एलीशन इस अभिव्यक्ति को सीधे एक अस्थायी + चाल का उपयोग करने के बजाय, str में ऑब्जेक्ट बनाने के लिए अनुमति देता है।

यह उन मामलों के लिए एक उपयोगी अनुकूलन है जहां एक निर्माणकर्ता को explicit घोषित किया जाता है। उदाहरण के लिए, हम उपरोक्त को func("foo") रूप में लिख सकते हैं, लेकिन केवल इसलिए कि string में एक अंतर्निहित कंस्ट्रक्टर है जो एक const char* से एक string परिवर्तित होता है। यदि वह कंस्ट्रक्टर explicit था, तो हमें explicit कंस्ट्रक्टर को कॉल करने के लिए एक अस्थायी का उपयोग करने के लिए मजबूर किया जाएगा। कॉपी एलीशन हमें अनावश्यक कॉपी / मूव करने से बचाता है।

नाम वापसी मान योग

यदि आप एक फ़ंक्शन से एक अंतराल अभिव्यक्ति लौटाते हैं, और यह अंतराल:

  • उस फ़ंक्शन के लिए एक स्वचालित चर स्थानीय का प्रतिनिधित्व करता है, जो return बाद नष्ट हो जाएगा
  • स्वचालित चर एक फ़ंक्शन पैरामीटर नहीं है
  • और चर का प्रकार फ़ंक्शन के रिटर्न प्रकार के समान है

यदि ये सभी मामले हैं, तो प्रतिरूप से प्रतिलिपि / चाल को समाप्त किया जा सकता है:

std::string func()
{
  std::string str("foo");
  //Do stuff
  return str;
}

अधिक जटिल मामले elision के लिए पात्र हैं, लेकिन जितना अधिक जटिल मामला है, कम संकलक वास्तव में इसे खत्म करने की संभावना होगी:

std::string func()
{
  std::string ret("foo");
  if(some_condition)
  {
    return "bar";
  }
  return ret;
}

संकलक अभी भी ret हो सकता है, लेकिन ऐसा करने की संभावना कम हो जाती है।

जैसा कि पहले उल्लेख किया गया है, मूल्य मापदंडों के लिए चीरा की अनुमति नहीं है।

std::string func(std::string str)
{
  str.assign("foo");
  //Do stuff
  return str; //No elision possible
}

इनिशियलाइज़ेशन एलिसेंस कॉपी करें

यदि आप किसी वैरिएबल को इनिशियलाइज़ करने के लिए एक प्रील्यूव एक्सप्रेशन का उपयोग करते हैं, और उस वैरिएबल में प्रील्यूव एक्सप्रेशन जैसा ही है, तो कॉपी किया जा सकता है।

std::string str = std::string("foo");

प्रतिलिपि आरंभीकरण प्रभावी रूप से इसे std::string str("foo"); में रूपांतरित करता है std::string str("foo"); (मामूली अंतर हैं)।

यह रिटर्न मान के साथ भी काम करता है:

std::string func()
{
  return std::string("foo");
}

std::string str = func();

कॉपी इलिशन के बिना, यह std::string के मूव कंस्ट्रक्टर को 2 कॉल के लिए उकसाएगा। कॉपी एलिशन इसे 1 या शून्य बार मूव कंस्ट्रक्टर को कॉल करने की अनुमति देता है, और अधिकांश कंपाइलर बाद के लिए विकल्प चुनेंगे।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow