C++
शब्दार्थ को स्थानांतरित करें
खोज…
शब्दार्थ को आगे बढ़ाएं
मूव शब्दार्थ C ++ में एक वस्तु को दूसरे में ले जाने का एक तरीका है। इसके लिए, हम पुरानी वस्तु को खाली करते हैं और नई वस्तु में वह सब कुछ रखते हैं।
इसके लिए, हमें यह समझना चाहिए कि एक संदर्भ क्या है। एक रेवल्यू रेफरेंस ( T&&
जहां T ऑब्जेक्ट ऑब्जेक्ट है) एक सामान्य संदर्भ ( T&
, जिसे अब लैवल्यू रेफरेंस कहा जाता है) से बहुत अलग नहीं है। लेकिन वे 2 अलग-अलग प्रकारों के रूप में कार्य करते हैं, और इसलिए, हम एक प्रकार या दूसरे को लेने वाले निर्माणकर्ता या कार्य कर सकते हैं, जो चाल शब्दार्थ के साथ काम करते समय आवश्यक होगा।
दो अलग-अलग प्रकारों की आवश्यकता के कारण दो अलग-अलग व्यवहारों को निर्दिष्ट करना है। लैवल्यू रेफरेंस कंस्ट्रक्टर्स नकल से संबंधित हैं, जबकि रेवल्यू रेफरेंस कंस्ट्रक्टर्स मूविंग से संबंधित हैं।
किसी ऑब्जेक्ट को स्थानांतरित करने के लिए, हम std::move(obj)
उपयोग करेंगे। यह फ़ंक्शन ऑब्जेक्ट के लिए एक संदर्भ देता है, जिससे हम उस ऑब्जेक्ट से डेटा को एक नए में चुरा सकते हैं। ऐसा करने के कई तरीके हैं जिनकी चर्चा नीचे की गई है।
नोट करने के लिए महत्वपूर्ण यह है कि std::move
Move का उपयोग केवल एक रेवल्यू संदर्भ बनाता है। दूसरे शब्दों में स्टेटमेंट std::move(obj)
की सामग्री को नहीं बदलता है, जबकि auto obj2 = std::move(obj)
(संभवतः) करता है।
कंस्ट्रक्टर को स्थानांतरित करें
कहें कि हमारे पास यह कोड स्निपेट है।
class A {
public:
int a;
int b;
A(const A &other) {
this->a = other.a;
this->b = other.b;
}
};
एक कॉपी कंस्ट्रक्टर बनाने के लिए, अर्थात्, एक फ़ंक्शन बनाने के लिए जो किसी ऑब्जेक्ट को कॉपी करता है और एक नया बनाता है, हम सामान्य रूप से ऊपर दिखाए गए सिंटैक्स का चयन करेंगे, हमारे पास ए के लिए एक कंस्ट्रक्टर होगा जो टाइप ए के किसी अन्य ऑब्जेक्ट के लिए संदर्भ लेता है, और हम विधि को मैन्युअल तरीके से कॉपी करेंगे।
वैकल्पिक रूप से, हम A(const A &) = default;
लिख सकते थे A(const A &) = default;
जो स्वचालित रूप से सभी सदस्यों से अधिक कॉपी करता है, इसके कॉपी कंस्ट्रक्टर का उपयोग करता है।
मूव कंस्ट्रक्टर बनाने के लिए, हालांकि, हम यहाँ एक लैवल्यू रेफरेंस के बजाय एक रवैल्यू रेफरेंस ले रहे होंगे।
class Wallet {
public:
int nrOfDollars;
Wallet() = default; //default ctor
Wallet(Wallet &&other) {
this->nrOfDollars = other.nrOfDollars;
other.nrOfDollars = 0;
}
};
कृपया ध्यान दें कि हम पुराने मानों को zero
सेट करते हैं। डिफ़ॉल्ट चाल निर्माणकर्ता ( Wallet(Wallet&&) = default;
) nrOfDollars
के मान की प्रतिलिपि बनाता है, क्योंकि यह एक POD है।
जैसा कि मूव शब्दार्थ को मूल उदाहरण से 'चोरी' करने की अनुमति देने के लिए डिज़ाइन किया गया है, यह विचार करना महत्वपूर्ण है कि इस चोरी के बाद मूल उदाहरण कैसा दिखना चाहिए। इस मामले में, यदि हम मूल्य को शून्य में नहीं बदलेंगे तो हमने डॉलर की राशि को खेल में दोगुना कर दिया होगा।
Wallet a;
a.nrOfDollars = 1;
Wallet b (std::move(a)); //calling B(B&& other);
std::cout << a.nrOfDollars << std::endl; //0
std::cout << b.nrOfDollars << std::endl; //1
इस प्रकार हमने एक पुराने से एक वस्तु का निर्माण किया है।
जबकि ऊपर एक सरल उदाहरण है, यह दर्शाता है कि चाल निर्माता क्या करना चाहता है। यह अधिक जटिल मामलों में अधिक उपयोगी हो जाता है, जैसे कि जब संसाधन प्रबंधन शामिल होता है।
// Manages operations involving a specified type.
// Owns a helper on the heap, and one in its memory (presumably on the stack).
// Both helpers are DefaultConstructible, CopyConstructible, and MoveConstructible.
template<typename T,
template<typename> typename HeapHelper,
template<typename> typename StackHelper>
class OperationsManager {
using MyType = OperationsManager<T, HeapHelper, StackHelper>;
HeapHelper<T>* h_helper;
StackHelper<T> s_helper;
// ...
public:
// Default constructor & Rule of Five.
OperationsManager() : h_helper(new HeapHelper<T>) {}
OperationsManager(const MyType& other)
: h_helper(new HeapHelper<T>(*other.h_helper)), s_helper(other.s_helper) {}
MyType& operator=(MyType copy) {
swap(*this, copy);
return *this;
}
~OperationsManager() {
if (h_helper) { delete h_helper; }
}
// Move constructor (without swap()).
// Takes other's HeapHelper<T>*.
// Takes other's StackHelper<T>, by forcing the use of StackHelper<T>'s move constructor.
// Replaces other's HeapHelper<T>* with nullptr, to keep other from deleting our shiny
// new helper when it's destroyed.
OperationsManager(MyType&& other) noexcept
: h_helper(other.h_helper),
s_helper(std::move(other.s_helper)) {
other.h_helper = nullptr;
}
// Move constructor (with swap()).
// Places our members in the condition we want other's to be in, then switches members
// with other.
// OperationsManager(MyType&& other) noexcept : h_helper(nullptr) {
// swap(*this, other);
// }
// Copy/move helper.
friend void swap(MyType& left, MyType& right) noexcept {
std::swap(left.h_helper, right.h_helper);
std::swap(left.s_helper, right.s_helper);
}
};
असाइनमेंट स्थानांतरित करें
इसी तरह से हम किसी वस्तु के मान को एक लैवल्यू रेफरेंस के साथ कैसे निर्दिष्ट कर सकते हैं, इसे कॉपी करते हुए, हम नए ऑब्जेक्ट के निर्माण के बिना किसी ऑब्जेक्ट से मूल्यों को दूसरे स्थान पर भी स्थानांतरित कर सकते हैं। इस कदम को हम असाइनमेंट कहते हैं। हम मानों को एक वस्तु से दूसरी मौजूदा वस्तु में स्थानांतरित करते हैं।
इसके लिए, हमें operator =
को अधिभारित करना होगा, ऐसा नहीं है कि यह एक लैवल्यू संदर्भ लेता है, जैसे कॉपी असाइनमेंट, लेकिन इतना है कि यह एक रिवैल्यू संदर्भ लेता है।
class A {
int a;
A& operator= (A&& other) {
this->a = other.a;
other.a = 0;
return *this;
}
};
यह चाल असाइनमेंट को परिभाषित करने के लिए विशिष्ट सिंटैक्स है। हम operator =
अधिभारित operator =
ताकि हम इसे एक रेवल्यू रेफरेंस फीड कर सकें और यह किसी अन्य ऑब्जेक्ट को असाइन कर सके।
A a;
a.a = 1;
A b;
b = std::move(a); //calling A& operator= (A&& other)
std::cout << a.a << std::endl; //0
std::cout << b.a << std::endl; //1
इस प्रकार, हम एक ऑब्जेक्ट को दूसरे में असाइन कर सकते हैं।
Std का उपयोग :: O (n to) से O (n) की जटिलता को कम करने के लिए कदम
C ++ 11 एक वस्तु को स्थानांतरित करने के लिए मुख्य भाषा और मानक पुस्तकालय समर्थन की शुरुआत की। विचार यह है कि एक वस्तु ओ एक अस्थायी है और एक तो इस तरह के एक गतिशील आवंटित बफर के रूप में सिर्फ चुराना ओ के संसाधन, करने के लिए अपने सुरक्षित एक तार्किक प्रतिलिपि चाहता है, छोड़ने ओ तार्किक लेकिन अभी भी नश्वर और copyable खाली जब।
मुख्य भाषा समर्थन मुख्य रूप से है
rvalue संदर्भ प्रकार बिल्डर
&&
, उदा,std::string&&
एक rvalue संदर्भ हैstd::string
, जो दर्शाता है कि ऑब्जेक्ट को संदर्भित एक अस्थायी है जिसके संसाधन बस पायलट किए जा सकते हैं (यानी स्थानांतरित)मूव कंस्ट्रक्टर
T( T&& )
लिए विशेष समर्थन, जो वास्तव में उन संसाधनों की प्रतिलिपि बनाने के बजाय निर्दिष्ट अन्य ऑब्जेक्ट से संसाधनों को कुशलतापूर्वक स्थानांतरित करने के लिए माना जाता है, औरमूव असाइनमेंट ऑपरेटर
auto operator=(T&&) -> T&
लिए विशेष समर्थनauto operator=(T&&) -> T&
, जो स्रोत से भी जाना चाहिए।
मानक लाइब्रेरी सपोर्ट मुख्य रूप से <utility>
हेडर से std::move
फंक्शन टेम्प्लेट है। यह फ़ंक्शन निर्दिष्ट ऑब्जेक्ट के लिए एक रेवल्यू संदर्भ बनाता है, यह दर्शाता है कि इसे से स्थानांतरित किया जा सकता है, जैसे कि यह एक अस्थायी था।
कंटेनर के लिए वास्तविक प्रतिलिपि आमतौर पर O ( n ) जटिलता की होती है, जहां n कंटेनर में आइटमों की संख्या होती है, जबकि गति O (1) है, निरंतर समय। और एक एल्गोरिथ्म के लिए है कि तार्किक प्रतियां कि कंटेनर n बार, यह आम तौर पर अव्यावहारिक O (n ²) बस रैखिक हे (एन) से जटिलता को कम कर सकते हैं।
सितंबर 19 2013 में डॉ। डॉब्स जर्नल में अपने लेख "कंटेनर्स दैट नेवर चेंज" में , एंड्रयू कोएनिग ने प्रोग्रामिंग की एक शैली का उपयोग करते समय एल्गोरिदम की अक्षमता का एक दिलचस्प उदाहरण प्रस्तुत किया, जहां आरंभीकरण के बाद चर अपरिवर्तनीय हैं। इस शैली के साथ छोरों को आम तौर पर पुनरावृत्ति का उपयोग करके व्यक्त किया जाता है। और कुछ एल्गोरिदम जैसे कि Collatz अनुक्रम उत्पन्न करने के लिए, पुनरावर्ती को तार्किक रूप से कंटेनर की प्रतिलिपि बनाने की आवश्यकता होती है:
// Based on an example by Andrew Koenig in his Dr. Dobbs Journal article
// “Containers That Never Change” September 19, 2013, available at
// <url: http://www.drdobbs.com/cpp/containters-that-never-change/240161543>
// Includes here, e.g. <vector>
namespace my {
template< class Item >
using Vector_ = /* E.g. std::vector<Item> */;
auto concat( Vector_<int> const& v, int const x )
-> Vector_<int>
{
auto result{ v };
result.push_back( x );
return result;
}
auto collatz_aux( int const n, Vector_<int> const& result )
-> Vector_<int>
{
if( n == 1 )
{
return result;
}
auto const new_result = concat( result, n );
if( n % 2 == 0 )
{
return collatz_aux( n/2, new_result );
}
else
{
return collatz_aux( 3*n + 1, new_result );
}
}
auto collatz( int const n )
-> Vector_<int>
{
assert( n != 0 );
return collatz_aux( n, Vector_<int>() );
}
} // namespace my
#include <iostream>
using namespace std;
auto main() -> int
{
for( int const x : my::collatz( 42 ) )
{
cout << x << ' ';
}
cout << '\n';
}
आउटपुट:
42 21 64 32 16 8 4 2
वैक्टर की नकल के कारण आइटम कॉपी संचालन की संख्या यहाँ लगभग ओ ( n ,) है, क्योंकि यह योग 1 + 2 + 3 + ... n है ।
जी ++ और विज़ुअल सी ++ के साथ ठोस संख्या में, collatz(42)
के उपरोक्त आह्वान का collatz(42)
होता है और वेक्टर कॉपी कंस्ट्रक्टर कॉल्स में 8 आइटम और 36 आइटम कॉपी ऑपरेशंस (8 * collatz(42)
= 28, प्लस कुछ) का collatz(42)
अनुक्रम हुआ।
इन सभी आइटम कॉपी ऑपरेशनों को केवल चलती वैक्टरों द्वारा हटाया जा सकता है जिनके मूल्यों की अब आवश्यकता नहीं है। ऐसा करने के लिए वेक्टर प्रकार के तर्कों के लिए const
और संदर्भ को हटाना आवश्यक है, वैक्टर को मूल्य से पारित करना। फ़ंक्शन रिटर्न पहले से ही स्वचालित रूप से अनुकूलित हैं। कॉल के लिए जहां वैक्टर पास किए जाते हैं, और फ़ंक्शन में फिर से आगे उपयोग नहीं किया जाता है, बस उन लोगों को स्थानांतरित करने के लिए वास्तव में कॉपी करने के बजाय std::move
लागू करें:
using std::move;
auto concat( Vector_<int> v, int const x )
-> Vector_<int>
{
v.push_back( x );
// warning: moving a local object in a return statement prevents copy elision [-Wpessimizing-move]
// See https://stackoverflow.com/documentation/c%2b%2b/2489/copy-elision
// return move( v );
return v;
}
auto collatz_aux( int const n, Vector_<int> result )
-> Vector_<int>
{
if( n == 1 )
{
return result;
}
auto new_result = concat( move( result ), n );
struct result; // Make absolutely sure no use of `result` after this.
if( n % 2 == 0 )
{
return collatz_aux( n/2, move( new_result ) );
}
else
{
return collatz_aux( 3*n + 1, move( new_result ) );
}
}
auto collatz( int const n )
-> Vector_<int>
{
assert( n != 0 );
return collatz_aux( n, Vector_<int>() );
}
यहां, जी ++ और विज़ुअल सी ++ कंपाइलर्स के साथ, वेक्टर कॉपी कंस्ट्रक्टर इनवोकेशन के कारण आइटम कॉपी ऑपरेशंस की संख्या ठीक 0 थी।
कलॉर्ज़ अनुक्रम की लंबाई में एल्गोरिदम आवश्यक रूप से O ( n ) है, लेकिन यह एक काफी नाटकीय सुधार है: O ( n O) → O ( n )।
कुछ भाषा समर्थन के साथ, शायद चलती का उपयोग कर सकता है और फिर भी अपनी प्रारंभिक और अंतिम चाल के बीच एक चर की अपरिहार्यता को व्यक्त और लागू कर सकता है , जिसके बाद उस चर का कोई भी उपयोग त्रुटि होना चाहिए। काश, C ++ 14 C ++ का समर्थन नहीं करता। लूप-फ्री कोड के लिए मूव के बाद कोई उपयोग नहीं किया जा सकता है, जिसे संबंधित नाम की पुनः घोषणा के माध्यम से अधूरा struct
रूप में लागू किया जा सकता है, जैसे कि struct result;
ऊपर, लेकिन यह बदसूरत है और अन्य प्रोग्रामर द्वारा समझे जाने की संभावना नहीं है; भी निदान काफी भ्रामक हो सकते हैं।
ऊपर जा रहा है, चलती के लिए C ++ भाषा और लाइब्रेरी समर्थन एल्गोरिथ्म जटिलता में भारी सुधार की अनुमति देता है, लेकिन समर्थन की अपूर्णता के कारण, कोड शुद्धता गारंटी और कोड स्पष्टता को त्यागने की कीमत पर जो कि const
प्रदान कर सकता है।
पूर्णता के लिए, इंस्ट्रूमेंटेड वेक्टर क्लास कॉपी कंस्ट्रक्टर इनवोकेशन के कारण आइटम कॉपी ऑपरेशन की संख्या को मापने के लिए उपयोग किया जाता है:
template< class Item >
class Copy_tracking_vector
{
private:
static auto n_copy_ops()
-> int&
{
static int value;
return value;
}
vector<Item> items_;
public:
static auto n() -> int { return n_copy_ops(); }
void push_back( Item const& o ) { items_.push_back( o ); }
auto begin() const { return items_.begin(); }
auto end() const { return items_.end(); }
Copy_tracking_vector(){}
Copy_tracking_vector( Copy_tracking_vector const& other )
: items_( other.items_ )
{ n_copy_ops() += items_.size(); }
Copy_tracking_vector( Copy_tracking_vector&& other )
: items_( move( other.items_ ) )
{}
};
कंटेनरों पर चाल शब्दार्थों का उपयोग करना
आप इसे कॉपी करने के बजाय एक कंटेनर ले जा सकते हैं:
void print(const std::vector<int>& vec) {
for (auto&& val : vec) {
std::cout << val << ", ";
}
std::cout << std::endl;
}
int main() {
// initialize vec1 with 1, 2, 3, 4 and vec2 as an empty vector
std::vector<int> vec1{1, 2, 3, 4};
std::vector<int> vec2;
// The following line will print 1, 2, 3, 4
print(vec1);
// The following line will print a new line
print(vec2);
// The vector vec2 is assigned with move assingment.
// This will "steal" the value of vec1 without copying it.
vec2 = std::move(vec1);
// Here the vec1 object is in an indeterminate state, but still valid.
// The object vec1 is not destroyed,
// but there's is no guarantees about what it contains.
// The following line will print 1, 2, 3, 4
print(vec2);
}
किसी स्थानांतरित वस्तु का पुनः उपयोग करें
आप किसी स्थानांतरित वस्तु का फिर से उपयोग कर सकते हैं:
void consumingFunction(std::vector<int> vec) {
// Some operations
}
int main() {
// initialize vec with 1, 2, 3, 4
std::vector<int> vec{1, 2, 3, 4};
// Send the vector by move
consumingFunction(std::move(vec));
// Here the vec object is in an indeterminate state.
// Since the object is not destroyed, we can assign it a new content.
// We will, in this case, assign an empty value to the vector,
// making it effectively empty
vec = {};
// Since the vector as gained a determinate value, we can use it normally.
vec.push_back(42);
// Send the vector by move again.
consumingFunction(std::move(vec));
}