वाह, यहाँ साफ करने के लिए बस इतना है ...
सबसे पहले, कॉपी और स्वैप हमेशा कॉपी असाइनमेंट को लागू करने का सही तरीका नहीं है। लगभग निश्चित रूप से dumb_array
, इस मामले में , यह एक उप-इष्टतम समाधान है।
कॉपी और स्वैप का उपयोग dumb_array
निम्न स्तर पर पूर्ण सुविधाओं के साथ सबसे महंगा ऑपरेशन डालने का एक उत्कृष्ट उदाहरण है। यह उन ग्राहकों के लिए एकदम सही है जो पूर्ण सुविधा चाहते हैं और प्रदर्शन दंड का भुगतान करने के लिए तैयार हैं। उन्हें वही मिलता है जो वे चाहते हैं।
लेकिन यह उन ग्राहकों के लिए विनाशकारी है जिन्हें पूर्ण सुविधा की आवश्यकता नहीं है और वे उच्चतम प्रदर्शन की तलाश कर रहे हैं। उनके लिए dumb_array
सॉफ्टवेयर का सिर्फ एक और टुकड़ा है जो उन्हें फिर से लिखना होगा क्योंकि यह बहुत धीमा है। dumb_array
अलग तरह से डिजाइन किया गया था , यह दोनों ग्राहकों को संतुष्ट कर सकता है जिसमें कोई भी समझौता नहीं है।
दोनों ग्राहकों को संतुष्ट करने की कुंजी सबसे निचले स्तर पर सबसे तेज संचालन का निर्माण करना है, और फिर अधिक खर्च पर फुलर सुविधाओं के लिए इसके ऊपर एपीआई जोड़ना है। यानी आपको मजबूत अपवाद गारंटी की आवश्यकता है, ठीक है, आप इसके लिए भुगतान करते हैं। आपको इसकी आवश्यकता नहीं है? यहाँ एक तेज़ उपाय है।
चलो ठोस हो जाओ: यहाँ के लिए तेज, बुनियादी अपवाद गारंटी कॉपी असाइनमेंट ऑपरेटर है dumb_array
:
dumb_array& operator=(const dumb_array& other)
{
if (this != &other)
{
if (mSize != other.mSize)
{
delete [] mArray;
mArray = nullptr;
mArray = other.mSize ? new int[other.mSize] : nullptr;
mSize = other.mSize;
}
std::copy(other.mArray, other.mArray + mSize, mArray);
}
return *this;
}
स्पष्टीकरण:
आधुनिक हार्डवेयर पर आप कर सकते हैं और अधिक महंगी चीजों में से एक ढेर के लिए एक यात्रा है। कुछ भी तुम ढेर के लिए एक यात्रा से बचने के लिए कर सकते हैं समय और प्रयास अच्छी तरह से बिताया है। ग्राहक dumb_array
अच्छी तरह से एक ही आकार के सरणियों को असाइन करना चाहते हैं। और जब वे करते हैं, तो आपको केवल एक memcpy
(छिपा हुआ std::copy
) होना चाहिए। आप एक ही आकार के एक नए सरणी को आवंटित नहीं करना चाहते हैं और फिर उसी आकार के पुराने को हटा दें!
अब आपके ग्राहकों के लिए जो वास्तव में मजबूत अपवाद सुरक्षा चाहते हैं:
template <class C>
C&
strong_assign(C& lhs, C rhs)
{
swap(lhs, rhs);
return lhs;
}
या हो सकता है कि अगर आप C ++ 11 में मूव असाइनमेंट का लाभ उठाना चाहते हैं जो कि होना चाहिए:
template <class C>
C&
strong_assign(C& lhs, C rhs)
{
lhs = std::move(rhs);
return lhs;
}
यदि dumb_array
ग्राहकों की गति की कीमत है, तो उन्हें फोन करना चाहिए operator=
। यदि उन्हें मजबूत अपवाद सुरक्षा की आवश्यकता है, तो जेनेरिक एल्गोरिदम हैं जो वे कॉल कर सकते हैं जो विभिन्न प्रकार की वस्तुओं पर काम करेंगे और केवल एक बार लागू होने की आवश्यकता होगी।
अब वापस मूल प्रश्न पर (जो इस समय एक टाइप-ओ है):
Class&
Class::operator=(Class&& rhs)
{
if (this == &rhs) // is this check needed?
{
// ...
}
return *this;
}
यह वास्तव में एक विवादास्पद प्रश्न है। कुछ कहेंगे हाँ, बिल्कुल, कुछ कहेंगे नहीं।
मेरी व्यक्तिगत राय नहीं है, आपको इस चेक की आवश्यकता नहीं है।
दलील:
जब कोई वस्तु एक रेवल्यू रेफरेंस से जुड़ती है तो यह दो चीजों में से एक है:
- अस्थायी।
- कॉल करने वाला एक ऑब्जेक्ट चाहता है कि आप विश्वास करें कि वह अस्थायी है।
यदि आपके पास किसी ऐसी वस्तु का संदर्भ है जो वास्तविक अस्थायी है, तो परिभाषा के अनुसार, आपके पास उस वस्तु का एक अनूठा संदर्भ है। यह संभवतः आपके पूरे कार्यक्रम में कहीं और संदर्भित नहीं किया जा सकता है। Ie this == &temporary
संभव नहीं है ।
अब यदि आपके ग्राहक ने आपसे झूठ बोला है और आपसे वादा किया है कि जब आप नहीं होते हैं तो आप एक अस्थायी हो जाते हैं, तो यह ग्राहक की जिम्मेदारी है कि आप सुनिश्चित करें कि आपको परवाह नहीं है। यदि आप वास्तव में सावधान रहना चाहते हैं, तो मेरा मानना है कि यह एक बेहतर कार्यान्वयन होगा:
Class&
Class::operator=(Class&& other)
{
assert(this != &other);
// ...
return *this;
}
यानी यदि आप कर रहे हैं एक आत्म संदर्भ पारित कर दिया, इस ग्राहक कि समाधान हो गया होगा की ओर से एक बग है।
पूर्णता के लिए, यहाँ एक चाल असाइनमेंट ऑपरेटर है dumb_array
:
dumb_array& operator=(dumb_array&& other)
{
assert(this != &other);
delete [] mArray;
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
return *this;
}
चाल असाइनमेंट के विशिष्ट उपयोग के मामले में, *this
एक स्थानांतरित-से-ऑब्जेक्ट होगा और इसलिए delete [] mArray;
एक नो-ऑप होना चाहिए। यह महत्वपूर्ण है कि कार्यान्वयन संभव के रूप में उपवास पर एक nullptr पर हटा दें।
चेतावनी:
कुछ लोग तर्क देंगे कि swap(x, x)
यह एक अच्छा विचार है, या सिर्फ एक आवश्यक बुराई है। और यह, यदि स्वैप डिफ़ॉल्ट स्वैप पर जाता है, तो स्व-चाल-असाइनमेंट का कारण बन सकता है।
मैं इससे सहमत नहीं है कि swap(x, x)
है कभी एक अच्छा विचार। यदि मेरे अपने कोड में पाया जाता है, तो मैं इसे एक प्रदर्शन बग पर विचार करूंगा और इसे ठीक करूंगा। लेकिन अगर आप इसे अनुमति देना चाहते हैं, तो महसूस करें कि swap(x, x)
केवल मूव-ऑन मूल्य पर सेल्फ-मूव-असाइनमेनेट करता है। और हमारे dumb_array
उदाहरण में यह पूरी तरह से हानिरहित होगा यदि हम केवल मुखर को छोड़ देते हैं, या इसे स्थानांतरित मामले से विवश करते हैं:
dumb_array& operator=(dumb_array&& other)
{
assert(this != &other || mSize == 0);
delete [] mArray;
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
return *this;
}
यदि आप दो स्थानांतरित-से (खाली) स्व-असाइन dumb_array
करते हैं, तो आप अपने प्रोग्राम में बेकार निर्देश डालने से अलग कुछ भी गलत नहीं करते हैं। यह एक ही अवलोकन वस्तुओं के विशाल बहुमत के लिए बनाया जा सकता है।
<
अपडेट करें>
मैंने इस मुद्दे को कुछ और सोचा है, और अपनी स्थिति को कुछ हद तक बदल दिया है। अब मेरा मानना है कि असाइनमेंट सेल्फ असाइनमेंट के प्रति सहनशील होना चाहिए, लेकिन यह कि कॉपी असाइनमेंट और मूव असाइनमेंट पर पोस्ट की शर्तें अलग-अलग हैं:
कॉपी असाइनमेंट के लिए:
x = y;
एक के बाद की स्थिति y
होनी चाहिए कि मूल्य में परिवर्तन नहीं होना चाहिए। जब &x == &y
यह पोस्टकंडिशन में अनुवाद होता है: सेल्फ कॉपी असाइनमेंट का मूल्य पर कोई प्रभाव नहीं होना चाहिए x
।
चाल असाइनमेंट के लिए:
x = std::move(y);
एक के पास एक ऐसी स्थिति होनी चाहिए, y
जिसकी वैध लेकिन अनिर्दिष्ट स्थिति हो। जब &x == &y
यह पोस्टकंडिशन में तब्दील हो जाता है: x
एक मान्य लेकिन अनिर्दिष्ट राज्य है। Ie सेल्फ मूव असाइनमेंट का नो-ऑप होना जरूरी है। लेकिन यह दुर्घटना नहीं होनी चाहिए। यह swap(x, x)
काम करने की स्थिति के अनुरूप है :
template <class T>
void
swap(T& x, T& y)
{
// assume &x == &y
T tmp(std::move(x));
// x and y now have a valid but unspecified state
x = std::move(y);
// x and y still have a valid but unspecified state
y = std::move(tmp);
// x and y have the value of tmp, which is the value they had on entry
}
ऊपर काम करता है, जब तक x = std::move(x)
दुर्घटना नहीं होती है। यह x
किसी भी वैध लेकिन अनिर्दिष्ट स्थिति में छोड़ सकता है ।
मैं dumb_array
इसे प्राप्त करने के लिए मूव असाइनमेंट ऑपरेटर को प्रोग्राम करने के तीन तरीके देखता हूं :
dumb_array& operator=(dumb_array&& other)
{
delete [] mArray;
// set *this to a valid state before continuing
mSize = 0;
mArray = nullptr;
// *this is now in a valid state, continue with move assignment
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
return *this;
}
उपरोक्त क्रियान्वयन बर्दाश्त स्वयं काम है, लेकिन *this
और other
आत्म-चाल काम के बाद एक शून्य आकार सरणी, कोई बात नहीं क्या की मूल मूल्य जा रहा है खत्म हो *this
गया है। यह ठीक है।
dumb_array& operator=(dumb_array&& other)
{
if (this != &other)
{
delete [] mArray;
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
}
return *this;
}
उपरोक्त कार्यान्वयन स्वयं असाइनमेंट को उसी तरह से सहन करता है जिस तरह से कॉपी असाइनमेंट ऑपरेटर करता है, इसे नो-ऑप बनाकर। यह भी ठीक है।
dumb_array& operator=(dumb_array&& other)
{
swap(other);
return *this;
}
उपरोक्त केवल तभी ठीक है जब dumb_array
संसाधनों को पकड़ न लिया जाए जो "तुरंत" नष्ट हो जाना चाहिए। उदाहरण के लिए यदि एकमात्र संसाधन मेमोरी है, तो उपरोक्त ठीक है। यदि dumb_array
संभवत: म्यूटेक्स लॉक या फ़ाइलों की खुली स्थिति को पकड़ सकता है, तो क्लाइंट यथोचित रूप से उन संसाधनों की अपेक्षा कर सकता है जो तुरंत जारी किए जाने वाले कदम असाइनमेंट के एलएचएस पर हैं और इसलिए यह कार्यान्वयन समस्याग्रस्त हो सकता है।
पहले की लागत दो अतिरिक्त स्टोर हैं। दूसरे की लागत एक परीक्षण और शाखा है। दोनों कार्य। दोनों C ++ 11 मानक में तालिका 22 MoveAssignable आवश्यकताओं की सभी आवश्यकताओं को पूरा करते हैं। तीसरा गैर-मेमोरी-रिसोर्स-चिंता का काम भी करता है।
सभी तीन कार्यान्वयन हार्डवेयर के आधार पर अलग-अलग लागत हो सकते हैं: एक शाखा कितनी महंगी है? क्या बहुत सारे रजिस्टर या बहुत कम हैं?
टेक-ऑफ यह है कि स्व-चाल-असाइनमेंट, स्व-कॉपी-असाइनमेंट के विपरीत, वर्तमान मूल्य को संरक्षित करने के लिए नहीं है।
<
/अपडेट करें>
एक अंतिम (उम्मीद है) ल्यूक डेंटन की टिप्पणी से प्रेरित संपादित करें:
यदि आप एक उच्च स्तरीय वर्ग लिख रहे हैं जो सीधे मेमोरी का प्रबंधन नहीं करता है (लेकिन आधार या सदस्य हो सकते हैं), तो चाल असाइनमेंट का सबसे अच्छा कार्यान्वयन अक्सर होता है:
Class& operator=(Class&&) = default;
यह प्रत्येक बेस और प्रत्येक सदस्य को बदले में असाइन करेगा, और इसमें एक this != &other
चेक शामिल नहीं होगा । यह आपको सबसे उच्च प्रदर्शन और बुनियादी अपवाद सुरक्षा देगा, यह मानते हुए कि आपके ठिकानों और सदस्यों के बीच किसी भी प्रकार के आक्रमणकारियों को बनाए रखने की आवश्यकता नहीं है। अपने ग्राहकों को मजबूत अपवाद सुरक्षा की मांग के लिए, उन्हें इंगित करें strong_assign
।