यह सही है कि std::move(x)
सिर्फ एक कास्ट करने के लिए है - विशेष रूप से एक xvalue करने के लिए , जैसा कि एक प्रचलन के विपरीत । और यह भी सच है कि move
कभी-कभी कलाकारों का नाम लोगों को भ्रमित कर देता है। हालांकि इस नामकरण का इरादा भ्रमित करने के लिए नहीं है, बल्कि आपके कोड को अधिक पठनीय बनाने के लिए है।
2002 में मूल चाल प्रस्ताव पर move
तारीखों का इतिहास । यह पत्र पहले रैवल्यू संदर्भ का परिचय देता है, और फिर दिखाता है कि अधिक कुशल कैसे लिखा जाए :std::swap
template <class T>
void
swap(T& a, T& b)
{
T tmp(static_cast<T&&>(a));
a = static_cast<T&&>(b);
b = static_cast<T&&>(tmp);
}
किसी को याद करना होगा कि इतिहास में इस बिंदु पर, केवल एक चीज जो " &&
" का अर्थ संभवतः तार्किक और हो सकता है । कोई भी रेवले के संदर्भ से परिचित नहीं था, और न ही एक रैलिंग में एक कॉवेल को कास्टिंग करने के निहितार्थ (जबकि ऐसा करने के लिए एक प्रति नहीं बना रहा था static_cast<T>(t)
)। तो इस कोड के पाठक स्वाभाविक रूप से सोचेंगे:
मुझे पता है कि कैसे swap
काम करना चाहिए (अस्थायी रूप से कॉपी करें और फिर मूल्यों का आदान-प्रदान करें), लेकिन उन बदसूरत जातियों का उद्देश्य क्या है ?!
यह भी ध्यान दें कि swap
सभी प्रकार के क्रमपरिवर्तन-संशोधन एल्गोरिदम के लिए वास्तव में सिर्फ एक स्टैंड-इन है। यह चर्चा ज्यादा है , बहुत बड़ी है swap
।
फिर प्रस्ताव प्रस्तुत किया जाने वाक्य रचना चीनी जो बदलता है static_cast<T&&>
अधिक पढ़ने योग्य चीज़ के बारे में बताता सटीक नहीं क्या , बल्कि क्यों :
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
यानी के move
लिए सिर्फ वाक्यविन्यास चीनी है static_cast<T&&>
, और अब कोड काफी विचारोत्तेजक है कि क्यों उन जातियों में हैं: स्थानांतरित शब्दार्थ को सक्षम करने के लिए!
यह समझना चाहिए कि इतिहास के संदर्भ में, इस बिंदु पर कुछ लोगों ने वास्तव में प्रतिद्वंद्वियों और चाल शब्दार्थों के बीच अंतरंग संबंध को समझा (हालांकि पेपर यह समझाने की कोशिश करता है कि:)
मूवमेंट तर्क दिए जाने पर स्वचालित रूप से चलन में आ जाएंगे। यह पूरी तरह से सुरक्षित है क्योंकि एक प्रतिद्वंद्विता से चलते हुए संसाधनों को कार्यक्रम के बाकी हिस्सों द्वारा नहीं देखा जा सकता है ( किसी और के पास किसी अंतर का पता लगाने के लिए प्रतिद्वंद्विता का संदर्भ नहीं है )।
यदि उस समय swap
इस तरह प्रस्तुत किया गया था:
template <class T>
void
swap(T& a, T& b)
{
T tmp(cast_to_rvalue(a));
a = cast_to_rvalue(b);
b = cast_to_rvalue(tmp);
}
तब लोगों ने उस पर नज़र डाली और कहा:
लेकिन आप प्रतिद्वंद्विता क्यों कर रहे हैं?
मुख्य बिंदु:
जैसा कि यह था, उपयोग करना move
, किसी ने कभी नहीं पूछा:
लेकिन आप क्यों बढ़ रहे हैं?
जैसे-जैसे साल बीतते गए और प्रस्ताव को परिष्कृत किया गया, आज के मूल्य वर्गों में अंतराल और प्रतिद्वंद्विता की धारणाओं को परिष्कृत किया गया :
(छवि बेशर्म से चोरी dirkgently )
और इसलिए आज, अगर हम swap
ठीक से कहना चाहते थे कि यह क्या कर रहा है, इसके बजाय , इसे अधिक क्यों दिखना चाहिए:
template <class T>
void
swap(T& a, T& b)
{
T tmp(set_value_category_to_xvalue(a));
a = set_value_category_to_xvalue(b);
b = set_value_category_to_xvalue(tmp);
}
और यह सवाल हर किसी को खुद से पूछना चाहिए कि क्या उपरोक्त कोड कमोबेश पठनीय है:
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
या मूल भी:
template <class T>
void
swap(T& a, T& b)
{
T tmp(static_cast<T&&>(a));
a = static_cast<T&&>(b);
b = static_cast<T&&>(tmp);
}
किसी भी घटना में, यात्री सी ++ प्रोग्रामर को पता होना चाहिए कि हुड के तहत move
, कलाकारों की तुलना में अधिक कुछ नहीं चल रहा है। और शुरुआती सी ++ प्रोग्रामर, कम से कम move
, के साथ सूचित किया जाएगा कि इरादे आरएचएस से स्थानांतरित करने के लिए है, जैसा कि आरएस से कॉपी करने के लिए विरोध किया जाता है , भले ही वे यह नहीं समझते कि यह कैसे पूरा होता है।
इसके अतिरिक्त, यदि कोई प्रोग्रामर किसी अन्य नाम के तहत इस कार्यक्षमता की इच्छा रखता है, तो इस कार्यक्षमता std::move
पर कोई एकाधिकार नहीं है, और इसके कार्यान्वयन में कोई गैर-पोर्टेबल भाषा जादू शामिल नहीं है। उदाहरण के लिए यदि कोई कोड करना चाहता था set_value_category_to_xvalue
, और इसके बजाय उसका उपयोग करना चाहता है , तो ऐसा करना तुच्छ है:
template <class T>
inline
constexpr
typename std::remove_reference<T>::type&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
C ++ 14 में यह और भी अधिक संक्षिप्त हो जाता है:
template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<std::remove_reference_t<T>&&>(t);
}
इसलिए यदि आप इतने झुके हुए हैं, तो अपने सजने-संवरने को static_cast<T&&>
सबसे अच्छा समझें, और शायद आप एक नए सर्वोत्तम अभ्यास को विकसित करेंगे (C ++ लगातार विकसित हो रहा है)।
तो move
उत्पन्न वस्तु कोड के संदर्भ में क्या करता है?
इस पर विचार करें test
:
void
test(int& i, int& j)
{
i = j;
}
के साथ संकलित clang++ -std=c++14 test.cpp -O3 -S
, यह इस ऑब्जेक्ट कोड का उत्पादन करता है:
__Z4testRiS_: ## @_Z4testRiS_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
movl (%rsi), %eax
movl %eax, (%rdi)
popq %rbp
retq
.cfi_endproc
अब यदि परीक्षण को बदल दिया जाए:
void
test(int& i, int& j)
{
i = std::move(j);
}
नहीं है बिल्कुल सब पर कोई परिवर्तन वस्तु कोड में। कोई भी इस परिणाम को सामान्य कर सकता है: तुच्छ चल वस्तुओं के लिए, std::move
कोई प्रभाव नहीं है।
अब इस उदाहरण को देखते हैं:
struct X
{
X& operator=(const X&);
};
void
test(X& i, X& j)
{
i = j;
}
यह उत्पन्न करता है:
__Z4testR1XS0_: ## @_Z4testR1XS0_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
popq %rbp
jmp __ZN1XaSERKS_ ## TAILCALL
.cfi_endproc
यदि आप इसके __ZN1XaSERKS_
माध्यम से चलते हैं c++filt
तो उत्पादन होता है X::operator=(X const&)
:। यहां कोई आश्चर्य नहीं। अब यदि परीक्षण को बदल दिया जाए:
void
test(X& i, X& j)
{
i = std::move(j);
}
फिर जेनरेट किए गए ऑब्जेक्ट कोड में अभी भी कोई बदलाव नहीं हुआ है। std::move
कुछ भी नहीं किया है, लेकिन j
एक प्रतिद्वंद्विता के लिए डाली है, और फिर उस प्रतिद्वंद्विता X
की प्रतिलिपि असाइनमेंट ऑपरेटर को बांधता है X
।
अब एक चाल असाइनमेंट ऑपरेटर को इसमें जोड़ने देता है X
:
struct X
{
X& operator=(const X&);
X& operator=(X&&);
};
अब वस्तु कोड करता है परिवर्तन:
__Z4testR1XS0_: ## @_Z4testR1XS0_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
popq %rbp
jmp __ZN1XaSEOS_ ## TAILCALL
.cfi_endproc
के बजाय बुलाया जा रहा है कि पता चलता है के __ZN1XaSEOS_
माध्यम से चल रहा है ।c++filt
X::operator=(X&&)
X::operator=(X const&)
और यह सब वहाँ है std::move
! यह रन टाइम पर पूरी तरह से गायब हो जाता है। इसका एकमात्र प्रभाव संकलन-समय पर है जहां यह परिवर्तन हो सकता है कि अधिभार क्या कहलाता है।
std::move
वास्तव में चलता है ..