c++ सैमांतिक्स क्या चल रहे हैं?




c++-faq c++11 (9)

मैंने C++0x बारे में स्कॉट मेयर्स के C++0x सॉफ्टवेयर इंजीनियरिंग रेडियो पॉडकास्ट साक्षात्कार को सुनना समाप्त कर दिया। अधिकांश नई विशेषताएं मुझे समझ में आईं, और मैं वास्तव में सी ++ 0x के बारे में उत्साहित हूं, एक के अपवाद के साथ। मुझे अभी भी सैमसंगिक्स नहीं मिल रहा है ... वे वास्तव में क्या हैं?


Semantics ले जाएँ rvalue संदर्भों पर आधारित हैं।
एक रैल्यू एक अस्थायी वस्तु है, जो अभिव्यक्ति के अंत में नष्ट होने जा रही है। वर्तमान सी ++ में, रावल केवल const संदर्भों से जुड़ते हैं। सी ++ 1 एक्स गैर- const रावल संदर्भों की अनुमति देगा, वर्तनी T&& , जो एक रावल्यू ऑब्जेक्ट्स के संदर्भ हैं।
चूंकि अभिव्यक्ति के अंत में एक रैल्यू मरने जा रहा है, इसलिए आप इसका डेटा चुरा सकते हैं। इसे किसी अन्य ऑब्जेक्ट में कॉपी करने के बजाय, आप इसके डेटा को इसमें ले जाते हैं

class X {
public: 
  X(X&& rhs) // ctor taking an rvalue reference, so-called move-ctor
    : data_()
  {
     // since 'x' is an rvalue object, we can steal its data
     this->swap(std::move(rhs));
     // this will leave rhs with the empty data
  }
  void swap(X&& rhs);
  // ... 
};

// ...

X f();

X x = f(); // f() returns result as rvalue, so this calls move-ctor

उपरोक्त कोड में, पुराने कंपाइलरों के साथ x f() का परिणाम X की कॉपी कन्स्ट्रक्टर का उपयोग कर X कॉपी किया गया है। यदि आपका कंपाइलर चलती अर्थशास्त्र का समर्थन करता है और X में एक चालक-निर्माता होता है, तो उसे इसके बजाय कहा जाता है। चूंकि इसकी rhs तर्क एक रैल्यू है , हम जानते हैं कि इसकी अब आवश्यकता नहीं है और हम इसका मूल्य चुरा सकते हैं।
तो मूल्य को f() से x तक लौटाए गए अज्ञात अस्थायी से स्थानांतरित किया जाता है (जबकि x का डेटा, एक खाली X प्रारंभ किया गया है, अस्थायी में स्थानांतरित हो जाता है, जो असाइनमेंट के बाद नष्ट हो जाएगा)।


To illustrate the need for move semantics , let's consider this example without move semantics:

Here's a function that takes an object of type T and returns an object of the same type T :

T f(T o) { return o; }
  //^^^ new object constructed

The above function uses call by value which means that when this function is called an object must be constructed to be used by the function.
Because the function also returns by value , another new object is constructed for the return value:

T b = f(a);
  //^ new object constructed

Two new objects have been constructed, one of which is a temporary object that's only used for the duration of the function.

When the new object is created from the return value, the copy constructor is called to copy the contents of the temporary object to the new object b. After the function completes, the temporary object used in the function goes out of scope and is destroyed.

Now, let's consider what a copy constructor does.

It must first initialize the object, then copy all the relevant data from the old object to the new one.
Depending on the class, maybe its a container with very much data, then that could represent much time and memory usage

// Copy constructor
T::T(T &old) {
    copy_data(m_a, old.m_a);
    copy_data(m_b, old.m_b);
    copy_data(m_c, old.m_c);
}

With move semantics it's now possible to make most of this work less unpleasant by simply moving the data rather than copying.

// Move constructor
T::T(T &&old) noexcept {
    m_a = std::move(old.m_a);
    m_b = std::move(old.m_b);
    m_c = std::move(old.m_c);
}

Moving the data involves re-associating the data with the new object. And no copy takes place at all.

This is accomplished with an rvalue reference.
An rvalue reference works pretty much like an lvalue reference with one important difference:
an rvalue reference can be moved and an lvalue cannot.

From cppreference.com :

To make strong exception guarantee possible, user-defined move constructors should not throw exceptions. In fact, standard containers typically rely on std::move_if_noexcept to choose between move and copy when container elements need to be relocated. If both copy and move constructors are provided, overload resolution selects the move constructor if the argument is an rvalue (either a prvalue such as a nameless temporary or an xvalue such as the result of std::move), and selects the copy constructor if the argument is an lvalue (named object or a function/operator returning lvalue reference). If only the copy constructor is provided, all argument categories select it (as long as it takes a reference to const, since rvalues can bind to const references), which makes copying the fallback for moving, when moving is unavailable. In many situations, move constructors are optimized out even if they would produce observable side-effects, see copy elision. A constructor is called a 'move constructor' when it takes an rvalue reference as a parameter. It is not obligated to move anything, the class is not required to have a resource to be moved and a 'move constructor' may not be able to move a resource as in the allowable (but maybe not sensible) case where the parameter is a const rvalue reference (const T&&).


मेरा पहला जवाब अर्थशास्त्र को स्थानांतरित करने के लिए एक बेहद सरल परिचय था, और इसे सरल रखने के उद्देश्य से कई विवरण छोड़े गए थे। हालांकि, अर्थशास्त्र को स्थानांतरित करने के लिए बहुत कुछ है, और मैंने सोचा कि अंतराल को भरने के दूसरे जवाब के लिए समय था। पहला जवाब पहले से ही पुराना है, और इसे पूरी तरह से अलग पाठ के साथ बदलने का अधिकार नहीं था। मुझे लगता है कि यह अभी भी पहली परिचय के रूप में अच्छी तरह से सेवा करता है। लेकिन अगर आप गहरी खुदाई करना चाहते हैं, तो पढ़ें :)

स्टीफन टी। Lavavej समय मूल्यवान प्रतिक्रिया प्रदान किया। बहुत बहुत धन्यवाद, स्टीफन!

परिचय

किसी अन्य ऑब्जेक्ट के बाहरी संसाधनों के स्वामित्व को लेने के लिए, कुछ स्थितियों के तहत, अर्थशास्त्र को ले जाएं। यह दो तरीकों से महत्वपूर्ण है:

  1. सस्ते चालों में सस्ते प्रतियों को बदलना। उदाहरण के लिए मेरा पहला जवाब देखें। ध्यान दें कि यदि कोई ऑब्जेक्ट कम से कम एक बाहरी संसाधन (या तो सीधे, या अप्रत्यक्ष रूप से इसके सदस्य ऑब्जेक्ट्स के माध्यम से प्रबंधित नहीं करता है), तो सेमेन्टिक्स को कॉपी सेमेन्टिक्स पर कोई लाभ नहीं मिलेगा। उस स्थिति में, ऑब्जेक्ट की प्रतिलिपि बनाना और ऑब्जेक्ट को ले जाना एक ही चीज़ है:

    class cannot_benefit_from_move_semantics
    {
        int a;        // moving an int means copying an int
        float b;      // moving a float means copying a float
        double c;     // moving a double means copying a double
        char d[64];   // moving a char array means copying a char array
    
        // ...
    };
    
  2. सुरक्षित "चाल-केवल" प्रकारों को कार्यान्वित करना; यही वह प्रकार है, जिसके लिए प्रतिलिपि समझ में नहीं आता है, लेकिन चलती है। उदाहरणों में अद्वितीय स्वामित्व अर्थशास्त्र के साथ ताले, फ़ाइल हैंडल और स्मार्ट पॉइंटर्स शामिल हैं। नोट: यह उत्तर std::auto_ptr पर चर्चा करता है, एक बहिष्कृत C ++ 98 मानक लाइब्रेरी टेम्पलेट, जिसे c ++ 11 में std::unique_ptr द्वारा प्रतिस्थापित किया गया था। इंटरमीडिएट सी ++ प्रोग्रामर शायद कम से कम कुछ हद तक std::auto_ptr परिचित हैं, और "चलने वाले अर्थशास्त्र" के कारण यह प्रदर्शित होता है, यह सी ++ 11 में चाल semantics पर चर्चा के लिए एक अच्छा प्रारंभिक बिंदु की तरह लगता है। YMMV।

एक कदम क्या है?

सी ++ 98 मानक लाइब्रेरी एक स्मार्ट पॉइंटर प्रदान करती है जिसमें अद्वितीय स्वामित्व अर्थशास्त्र है जिसे std::auto_ptr<T> कहा जाता है। यदि आप auto_ptr अपरिचित हैं, तो इसका उद्देश्य यह गारंटी देना है कि गतिशील रूप से आवंटित ऑब्जेक्ट हमेशा अपवादों के सामने भी जारी किया जाता है:

{
    std::auto_ptr<Shape> a(new Triangle);
    // ...
    // arbitrary code, could throw exceptions
    // ...
}   // <--- when a goes out of scope, the triangle is deleted automatically

auto_ptr बारे में असामान्य बात इसकी "प्रतिलिपि" व्यवहार है:

auto_ptr<Shape> a(new Triangle);

      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        |
        |
  +-----|---+
  |   +-|-+ |
a | p | | | |
  |   +---+ |
  +---------+

auto_ptr<Shape> b(a);

      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        +----------------------+
                               |
  +---------+            +-----|---+
  |   +---+ |            |   +-|-+ |
a | p |   | |          b | p | | | |
  |   +---+ |            |   +---+ |
  +---------+            +---------+

ध्यान दें कि b साथ प्रारंभिकरण त्रिकोण की प्रतिलिपि नहीं करता है, बल्कि इसके बजाय त्रिभुज के स्वामित्व को a से b तक स्थानांतरित करता है। हम यह भी कहते हैं " a b में स्थानांतरित हो गया है " या "त्रिभुज को b से स्थानांतरित किया जाता है "। यह भ्रमित लग सकता है, क्योंकि त्रिभुज हमेशा स्मृति में एक ही स्थान पर रहता है।

ऑब्जेक्ट को स्थानांतरित करने के लिए कुछ संसाधनों के स्वामित्व को स्थानांतरित करना है, यह किसी अन्य ऑब्जेक्ट में प्रबंधित होता है।

auto_ptr की कॉपी कन्स्ट्रक्टर शायद इस तरह कुछ दिखता है (कुछ हद तक सरलीकृत):

auto_ptr(auto_ptr& source)   // note the missing const
{
    p = source.p;
    source.p = 0;   // now the source no longer owns the object
}

खतरनाक और हानिरहित चालें

auto_ptr बारे में खतरनाक बात यह है कि एक प्रतिलिपि की तरह एक वाक्य की तरह दिखता है वास्तव में एक कदम है। एक auto_ptr से auto_ptr पर सदस्य फ़ंक्शन को कॉल करने का प्रयास करने से अपरिभाषित व्यवहार का आह्वान होगा, इसलिए आपको बहुत सावधान रहना होगा कि इसे auto_ptr का उपयोग न करने के बाद:

auto_ptr<Shape> a(new Triangle);   // create triangle
auto_ptr<Shape> b(a);              // move a into b
double area = a->area();           // undefined behavior

लेकिन auto_ptr हमेशा खतरनाक नहीं है। फैक्टरी फ़ंक्शन auto_ptr लिए बिल्कुल सही उपयोग केस हैं:

auto_ptr<Shape> make_triangle()
{
    return auto_ptr<Shape>(new Triangle);
}

auto_ptr<Shape> c(make_triangle());      // move temporary into c
double area = make_triangle()->area();   // perfectly safe

ध्यान दें कि दोनों उदाहरण एक ही वाक्य रचनात्मक पैटर्न का पालन करते हैं:

auto_ptr<Shape> variable(expression);
double area = expression->area();

और फिर भी, उनमें से एक अपरिभाषित व्यवहार का आह्वान करता है, जबकि दूसरा नहीं करता है। तो अभिव्यक्तियों और make_triangle() अभिव्यक्तियों के बीच क्या अंतर है? क्या वे दोनों एक ही प्रकार के नहीं हैं? वास्तव में वे हैं, लेकिन उनके पास अलग-अलग मूल्य श्रेणियां हैं

मूल्य श्रेणियां

जाहिर है, अभिव्यक्ति के बीच कुछ गहरा अंतर होना चाहिए जो एक auto_ptr वैरिएबल को इंगित करता है, और अभिव्यक्ति make_triangle() जो एक फ़ंक्शन की कॉल को इंगित करता है जो एक auto_ptr मान द्वारा देता है, इस प्रकार हर बार इसे ताज़ा अस्थायी auto_ptr ऑब्जेक्ट auto_ptr है । a lvalue का एक उदाहरण है, जबकि make_triangle() एक make_triangle() का एक उदाहरण है।

जैसे कि लालसा से चलना खतरनाक है, क्योंकि हम बाद में एक सदस्य समारोह को कॉल करने का प्रयास कर सकते हैं, अपरिभाषित व्यवहार का आह्वान करते हैं। दूसरी तरफ, make_triangle() जैसे रैल्यू से आगे बढ़ना पूरी तरह से सुरक्षित है, क्योंकि कॉपी कन्स्ट्रक्टर ने अपना काम पूरा करने के बाद, हम अस्थायी रूप से फिर से उपयोग नहीं कर सकते हैं। कोई अभिव्यक्ति नहीं है जो अस्थायी कहा जाता है; अगर हम बस make_triangle() लिखते हैं, तो हमें एक अलग अस्थायी मिलता है। वास्तव में, अस्थायी से अस्थायी पहले से ही अगली पंक्ति पर चला गया है:

auto_ptr<Shape> c(make_triangle());
                                  ^ the moved-from temporary dies right here

ध्यान दें कि अक्षर l और r पास बाएं हाथ की ओर एक ऐतिहासिक उत्पत्ति है और एक असाइनमेंट के दायीं तरफ है। यह अब सी ++ में सच नहीं है, क्योंकि वहां ऐसे अंतराल हैं जो असाइनमेंट के बाईं ओर दिखाई नहीं दे सकते हैं (जैसे एरे या उपयोगकर्ता द्वारा परिभाषित प्रकारों को असाइनमेंट ऑपरेटर के बिना), और ऐसे रावल हैं जो (वर्ग प्रकार के सभी राव्यू एक असाइनमेंट ऑपरेटर के साथ)।

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

रावल संदर्भ

अब हम समझते हैं कि अंतराल से आगे बढ़ना खतरनाक है, लेकिन राजस्व से आगे बढ़ना हानिरहित है। यदि सी ++ में रावल्यू तर्कों से लैवल्यू तर्कों को अलग करने के लिए भाषा समर्थन था, तो हम या तो पूरी तरह से लालसा से आगे बढ़ने से मना कर सकते हैं, या कम से कम कॉल साइट पर स्पष्ट रूप से लाभार्थियों से आगे बढ़ सकते हैं, ताकि हम दुर्घटना से आगे नहीं बढ़ सकें।

इस समस्या का सी ++ 11 का जवाब रैवलू संदर्भ है । एक रावल्यू संदर्भ एक नया प्रकार का संदर्भ है जो केवल रावलों से जुड़ा होता है, और सिंटैक्स X&& एंड X&& । अच्छा पुराना संदर्भ X& अब एक लवल संदर्भ के रूप में जाना जाता है। (ध्यान दें कि X&& किसी संदर्भ का संदर्भ नहीं है; सी ++ में ऐसी कोई चीज़ नहीं है।)

अगर हम मिश्रण में const फेंक देते हैं, तो हमारे पास पहले से ही चार अलग-अलग प्रकार के संदर्भ हैं। वे किस प्रकार के अभिव्यक्ति X को बांध सकते हैं?

            lvalue   const lvalue   rvalue   const rvalue
---------------------------------------------------------              
X&          yes
const X&    yes      yes            yes      yes
X&&                                 yes
const X&&                           yes      yes

अभ्यास में, आप const X&& बारे में भूल सकते हैं। राजस्व से पढ़ने के लिए प्रतिबंधित होने के नाते बहुत उपयोगी नहीं है।

एक रेवल्यू संदर्भ X&& एंड X&& एक नया प्रकार का संदर्भ है जो केवल रावलों से जुड़ा होता है।

लागू रूपांतरण

रावल संदर्भ कई संस्करणों के माध्यम से चला गया। संस्करण 2.1 के बाद से, एक रैल्यू संदर्भ X&& एक अलग प्रकार Y की सभी मान श्रेणियों से भी जुड़ा हुआ है, बशर्ते Y से X तक एक अंतर्निहित रूपांतरण हो। उस स्थिति में, प्रकार का अस्थायी X बनाया गया है, और रावल्यू संदर्भ उस अस्थायी से जुड़ा हुआ है:

void some_function(std::string&& r);

some_function("hello world");

उपर्युक्त उदाहरण में, "hello world" टाइप const char[12] का एक रावल्यू है। चूंकि const char[12] से const char* से std::string माध्यम से एक अंतर्निहित रूपांतरण होता है, इसलिए अस्थायी प्रकार की std::string बनाई जाती है, और r उस अस्थायी तक सीमित है। यह उन मामलों में से एक है जहां रावल (अभिव्यक्ति) और अस्थायी (वस्तुओं) के बीच भेद थोड़ा धुंधला है।

रचनाकारों को ले जाएं

X&& पैरामीटर वाले फ़ंक्शन का एक उपयोगी उदाहरण X&& कन्स्ट्रक्टर X::X(X&& source) । इसका उद्देश्य स्रोत से मौजूदा संसाधन में प्रबंधित संसाधन के स्वामित्व को स्थानांतरित करना है।

सी ++ 11 में, std::auto_ptr<T> को std::unique_ptr<T> द्वारा प्रतिस्थापित किया गया है जो std::unique_ptr<T> संदर्भों का लाभ उठाता है। मैं unique_ptr सरलीकृत संस्करण को विकसित और चर्चा unique_ptr । सबसे पहले, हम एक कच्चे सूचक को समाहित करते हैं और ऑपरेटरों को ओवरलोड करते हैं -> और * , इसलिए हमारी कक्षा एक सूचक की तरह महसूस करती है:

template<typename T>
class unique_ptr
{
    T* ptr;

public:

    T* operator->() const
    {
        return ptr;
    }

    T& operator*() const
    {
        return *ptr;
    }

कन्स्ट्रक्टर ऑब्जेक्ट का स्वामित्व लेता है, और विनाशक इसे हटा देता है:

    explicit unique_ptr(T* p = nullptr)
    {
        ptr = p;
    }

    ~unique_ptr()
    {
        delete ptr;
    }

अब दिलचस्प हिस्सा आता है, चालक कन्स्ट्रक्टर:

    unique_ptr(unique_ptr&& source)   // note the rvalue reference
    {
        ptr = source.ptr;
        source.ptr = nullptr;
    }

यह चालक कन्स्ट्रक्टर वही करता है जो auto_ptr कॉपी कन्स्ट्रक्टर ने किया था, लेकिन इसे केवल रावल के साथ ही प्रदान किया जा सकता है:

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);                 // error
unique_ptr<Shape> c(make_triangle());   // okay

दूसरी पंक्ति संकलित करने में विफल रहता है, क्योंकि a lvalue है, लेकिन पैरामीटर unique_ptr&& source केवल rvalues ​​के लिए बाध्य हो सकता है। यह वही है जो हम चाहते थे; खतरनाक चाल कभी भी निहित नहीं होना चाहिए। तीसरी पंक्ति सिर्फ ठीक है, क्योंकि make_triangle() एक make_triangle() है। चालक कन्स्ट्रक्टर अस्थायी से c तक स्वामित्व स्थानांतरित कर देगा। फिर, यह वही है जो हम चाहते थे।

चालक कन्स्ट्रक्टर एक प्रबंधित संसाधन के स्वामित्व को वर्तमान ऑब्जेक्ट में स्थानांतरित करता है।

असाइनमेंट ऑपरेटरों को ले जाएं

आखिरी गायब टुकड़ा चाल असाइनमेंट ऑपरेटर है। इसका काम पुराना संसाधन जारी करना और नए तर्क को अपने तर्क से प्राप्त करना है:

    unique_ptr& operator=(unique_ptr&& source)   // note the rvalue reference
    {
        if (this != &source)    // beware of self-assignment
        {
            delete ptr;         // release the old resource

            ptr = source.ptr;   // acquire the new resource
            source.ptr = nullptr;
        }
        return *this;
    }
};

ध्यान दें कि चाल असाइनमेंट ऑपरेटर का यह कार्यान्वयन कैसे विनाशक और चालक कन्स्ट्रक्टर दोनों के तर्क को डुप्लिकेट करता है। क्या आप कॉपी-एंड-स्वैप मुहावरे से परिचित हैं? इसे सैमांतिक्स को स्थानांतरित करने के लिए भी लागू किया जा सकता है जैसे चाल-और-स्वैप मुहावरे:

    unique_ptr& operator=(unique_ptr source)   // note the missing reference
    {
        std::swap(ptr, source.ptr);
        return *this;
    }
};

अब वह source अद्वितीय_ unique_ptr प्रकार का एक चर है, इसे unique_ptr द्वारा प्रारंभ किया जाएगा; यानी, तर्क पैरामीटर में ले जाया जाएगा। तर्क अभी भी एक रैल्यू होने की आवश्यकता है, क्योंकि चालक कन्स्ट्रक्टर के पास एक रावल्यू संदर्भ पैरामीटर है। जब नियंत्रण प्रवाह operator= की समाप्ति ब्रेस तक पहुंच जाता है, तो source पुराने क्षेत्र को स्वचालित रूप से जारी करने के दायरे से बाहर हो जाता है।

चाल असाइनमेंट ऑपरेटर पुराने संसाधन को जारी करते हुए, मौजूदा ऑब्जेक्ट में प्रबंधित संसाधन के स्वामित्व को स्थानांतरित करता है। चाल-और-स्वैप मुहावरे कार्यान्वयन को सरल बनाता है।

लालच से आगे बढ़ना

कभी-कभी, हम अंतराल से आगे बढ़ना चाहते हैं। यही है, कभी-कभी हम चाहते हैं कि संकलक एक लवल्यू का इलाज करे जैसे कि यह एक रावल्यू था, इसलिए यह चालक कन्स्ट्रक्टर का आह्वान कर सकता है, भले ही यह संभावित रूप से असुरक्षित हो। इस उद्देश्य के लिए, सी ++ 11 एक मानक लाइब्रेरी फ़ंक्शन टेम्पलेट प्रदान करता है जिसे हेडर <utility> अंदर std::move कहा जाता है। यह नाम थोड़ा दुर्भाग्यपूर्ण है, क्योंकि std::move बस एक रैवल्यू के लिए एक लाभा का कारण बनता है; यह अपने आप से कुछ भी नहीं ले जाता है। यह केवल चलने में सक्षम बनाता है । शायद इसे std::cast_to_rvalue या std::enable_move नाम दिया जाना चाहिए था, लेकिन हम अब तक नाम से अटक गए हैं।

यहां बताया गया है कि आप स्पष्ट रूप से एक लालसा से कैसे आगे बढ़ते हैं:

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);              // still an error
unique_ptr<Shape> c(std::move(a));   // okay

ध्यान दें कि तीसरी पंक्ति के बाद, अब एक त्रिकोण का मालिक नहीं है। यह ठीक है, क्योंकि स्पष्ट रूप से std::move(a) लिखकर, हमने अपने इरादों को स्पष्ट कर दिया: "प्रिय कन्स्ट्रक्टर, जो कुछ भी आप चाहते हैं उसे c शुरू करने के लिए करें; मुझे अब और परवाह नहीं है। a साथ आपका रास्ता। "

std::move(some_lvalue) एक std::move(some_lvalue) लिए एक std::move(some_lvalue) का कारण बनता है, इस प्रकार बाद में चलने में सक्षम बनाता है।

XValues

ध्यान दें कि भले ही std::move(a) एक रावल्यू है, फिर भी इसका मूल्यांकन एक अस्थायी वस्तु नहीं बनाता है। इस conundrum समिति को तीसरी मूल्य श्रेणी शुरू करने के लिए मजबूर किया। कुछ ऐसा जो कि एक रावल्यू संदर्भ से बंधे जा सकते हैं, भले ही यह पारंपरिक अर्थ में एक रैल्यू नहीं है, इसे xvalue (eXpiring value) कहा जाता है। पारंपरिक राजस्व का नाम बदलकर (शुद्ध रावल) कर दिया गया था।

दोनों प्रावधान और xvalues ​​राजस्व हैं। Xvalues ​​और lvalues ​​दोनों glvalues (सामान्यीकृत lvalues) हैं। आरेख के साथ संबंधों को समझना आसान होता है:

        expressions
          /     \
         /       \
        /         \
    glvalues   rvalues
      /  \       /  \
     /    \     /    \
    /      \   /      \
lvalues   xvalues   prvalues

ध्यान दें कि केवल xvalues ​​वास्तव में नए हैं; शेष सिर्फ नामकरण और समूहकरण के कारण है।

सी ++ 98 रैल्यू सी ++ 11 में प्रचलित के रूप में जाना जाता है। "Prvalue" के साथ पिछले अनुच्छेदों में मानसिक रूप से "रावल्यू" की सभी घटनाओं को प्रतिस्थापित करें।

कार्यों से बाहर निकलना

अब तक, हमने स्थानीय चर, और फ़ंक्शन पैरामीटर में आंदोलन देखा है। लेकिन विपरीत दिशा में चलना भी संभव है। यदि कोई फ़ंक्शन मान द्वारा लौटाता है, तो कॉल साइट पर कुछ ऑब्जेक्ट (शायद एक स्थानीय चर या अस्थायी, लेकिन किसी भी प्रकार का ऑब्जेक्ट हो सकता है) return कथन के बाद अभिव्यक्ति के साथ अभिव्यक्ति के साथ आरंभ किया जाता है:

unique_ptr<Shape> make_triangle()
{
    return unique_ptr<Shape>(new Triangle);
}          \-----------------------------/
                  |
                  | temporary is moved into c
                  |
                  v
unique_ptr<Shape> c(make_triangle());

शायद आश्चर्य की बात है, स्वचालित वस्तुओं (स्थानीय चर जो static रूप में घोषित नहीं किए जाते हैं) को भी कार्यों से बाहर ले जाया जा सकता है:

unique_ptr<Shape> make_square()
{
    unique_ptr<Shape> result(new Square);
    return result;   // note the missing std::move
}

चालक कन्स्ट्रक्टर कैसे एक तर्क के रूप में lvalue result स्वीकार करता है? result का दायरा समाप्त होने वाला है, और इसे अवांछित ढेर के दौरान नष्ट कर दिया जाएगा। बाद में कोई भी शिकायत नहीं कर सकता था कि result किसी भी तरह बदल गया था; जब कॉलर पर नियंत्रण प्रवाह वापस आ जाता है, तो result अब मौजूद नहीं होता है! इसी कारण से, सी ++ 11 में एक विशेष नियम है जो std::move लिखने के बिना स्वचालित ऑब्जेक्ट्स को फ़ंक्शंस से वापस करने की अनुमति देता है। वास्तव में, आपको स्वचालित ऑब्जेक्ट्स को फ़ंक्शंस से बाहर ले std::move लिए std::move का उपयोग कभी नहीं करना चाहिए, क्योंकि यह "नामित वापसी मान अनुकूलन" (एनआरवीओ) को रोकता है।

कार्यों से बाहर स्वचालित वस्तुओं को std::move करने के लिए कभी भी std::move का उपयोग न करें।

ध्यान दें कि दोनों कारखाने के कार्यों में, रिटर्न प्रकार एक मान है, न कि एक रावल्यू संदर्भ। रावल संदर्भ अभी भी संदर्भ हैं, और हमेशा के रूप में, आपको किसी स्वचालित ऑब्जेक्ट का संदर्भ कभी नहीं देना चाहिए; यदि आपने संकलक को अपने कोड को स्वीकार करने में धोखा दिया है तो कॉलर एक खतरनाक संदर्भ के साथ समाप्त होगा, जैसे:

unique_ptr<Shape>&& flawed_attempt()   // DO NOT DO THIS!
{
    unique_ptr<Shape> very_bad_idea(new Square);
    return std::move(very_bad_idea);   // WRONG!
}

Rvalue संदर्भ द्वारा स्वचालित वस्तुओं को कभी वापस न करें। चलना विशेष रूप से std::move कन्स्ट्रक्टर द्वारा किया जाता है, न कि std::move द्वारा, और केवल एक रावल्यू संदर्भ के लिए एक रावल्यू बाध्यकारी द्वारा।

सदस्यों में आगे बढ़ना

जल्द या बाद में, आप इस तरह कोड लिखने जा रहे हैं:

class Foo
{
    unique_ptr<Shape> member;

public:

    Foo(unique_ptr<Shape>&& parameter)
    : member(parameter)   // error
    {}
};

असल में, संकलक शिकायत करेगा कि parameter एक लाभा है। यदि आप इसके प्रकार को देखते हैं, तो आप एक रावल्यू संदर्भ देखते हैं, लेकिन एक रावल्यू संदर्भ का अर्थ है "एक संदर्भ जो एक रावल्यू से बंधे हैं"; इसका मतलब यह नहीं है कि संदर्भ स्वयं एक रावल्यू है! वास्तव में, parameter सिर्फ नाम के साथ एक साधारण चर है। जब आप कन्स्ट्रक्टर के शरीर के अंदर जितनी बार चाहें parameter उपयोग कर सकते हैं, और यह हमेशा एक ही ऑब्जेक्ट को इंगित करता है। इससे स्पष्ट रूप से आगे बढ़ना खतरनाक होगा, इसलिए भाषा इसे मना कर देती है।

एक नामित रावल्यू संदर्भ किसी अन्य चर की तरह, एक लवल्यू है।

समाधान चाल को मैन्युअल रूप से सक्षम करना है:

class Foo
{
    unique_ptr<Shape> member;

public:

    Foo(unique_ptr<Shape>&& parameter)
    : member(std::move(parameter))   // note the std::move
    {}
};

आप तर्क दे सकते हैं कि member के प्रारंभ के बाद parameter का उपयोग नहीं किया जाता है। वापसी मूल्यों के साथ चुपचाप std::move डालने के लिए कोई विशेष नियम क्यों नहीं है? शायद क्योंकि यह कंपाइलर कार्यान्वयन पर बहुत अधिक बोझ होगा। उदाहरण के लिए, क्या होगा यदि कन्स्ट्रक्टर बॉडी किसी अन्य अनुवाद इकाई में थी? इसके विपरीत, रिटर्न वैल्यू नियम को केवल यह निर्धारित करने के लिए प्रतीक तालिकाओं की जांच करनी होती है कि return कीवर्ड के बाद पहचानकर्ता स्वचालित ऑब्जेक्ट को इंगित करता है या नहीं।

आप मूल्य से parameter भी पास कर सकते हैं। unique_ptr जैसे-जैसे unique_ptr , ऐसा लगता है कि अभी तक कोई स्थापित मुहावरे नहीं है। व्यक्तिगत रूप से, मैं मूल्य से गुजरना पसंद करता हूं, क्योंकि यह इंटरफ़ेस में कम अव्यवस्था का कारण बनता है।

विशेष सदस्य कार्य

सी ++ 98 मांग पर तीन विशेष सदस्य कार्यों की घोषणा करता है, यानी, जब उन्हें कहीं आवश्यकता होती है: कॉपी कन्स्ट्रक्टर, कॉपी असाइनमेंट ऑपरेटर और विनाशक।

X::X(const X&);              // copy constructor
X& X::operator=(const X&);   // copy assignment operator
X::~X();                     // destructor

रावल संदर्भ कई संस्करणों के माध्यम से चला गया। संस्करण 3.0 के बाद से, सी ++ 11 मांग पर दो अतिरिक्त विशेष सदस्य कार्यों की घोषणा करता है: चालक कन्स्ट्रक्टर और चाल असाइनमेंट ऑपरेटर। ध्यान दें कि न तो वीसी 10 और न ही वीसी 11 संस्करण 3.0 के अनुरूप है, इसलिए आपको उन्हें स्वयं लागू करना होगा।

X::X(X&&);                   // move constructor
X& X::operator=(X&&);        // move assignment operator

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

अभ्यास में इन नियमों का क्या अर्थ है?

यदि आप अप्रबंधित संसाधनों के बिना कक्षा लिखते हैं, तो पांच विशेष सदस्य कार्यों में से किसी एक को घोषित करने की आवश्यकता नहीं है, और आपको सही प्रतिलिपि अर्थशास्त्र मिलेगा और अर्थशास्त्र को मुफ्त में ले जाया जाएगा। अन्यथा, आपको विशेष सदस्य कार्यों को स्वयं लागू करना होगा। बेशक, यदि आपकी कक्षा को चलने वाले अर्थशास्त्र से लाभ नहीं होता है, तो विशेष कदम संचालन को लागू करने की कोई आवश्यकता नहीं है।

ध्यान दें कि प्रतिलिपि असाइनमेंट ऑपरेटर और चाल असाइनमेंट ऑपरेटर को एक एकल, एकीकृत असाइनमेंट ऑपरेटर में जोड़ा जा सकता है, जो इसके तर्क को मूल्य से लेता है:

X& X::operator=(X source)    // unified assignment operator
{
    swap(source);            // see my first answer for an explanation
    return *this;
}

इस तरह, पांच से चार तक बूंदों को लागू करने के लिए विशेष सदस्य कार्यों की संख्या। यहां अपवाद-सुरक्षा और दक्षता के बीच एक व्यापार है, लेकिन मैं इस मुद्दे पर एक विशेषज्ञ नहीं हूं।

अग्रेषण संदर्भ (जिसे previously यूनिवर्सल संदर्भ के रूप में जाना जाता था)

निम्न फ़ंक्शन टेम्पलेट पर विचार करें:

template<typename T>
void foo(T&&);

आप उम्मीद कर सकते हैं कि T&& एंड एक्स केवल रावल से जुड़ जाए, क्योंकि पहली नज़र में, यह एक रावल्यू संदर्भ की तरह दिखता है। जैसा कि यह पता चला है, T&& एंड एस भी lvalues ​​से बांधता है:

foo(make_triangle());   // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a);                 // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&

यदि तर्क X प्रकार का एक रैल्यू है, T को X होने के लिए घटाया जाता है, इसलिए T&& X&& अर्थ X&& T&& X&& । यह वही है जो कोई उम्मीद करेगा। लेकिन यदि तर्क एक विशेष नियम के कारण टाइप X का एक स्तर है, T को X& निकाला जाता है, इसलिए T&& X& && अर्थ X& && जैसे कुछ होगा। लेकिन चूंकि सी ++ में अभी भी संदर्भों के संदर्भों की कोई धारणा नहीं है, इसलिए X& && टाइप X& && में ध्वस्त हो गया है। यह पहली बार भ्रमित और बेकार लग सकता है, लेकिन सही फॉरवर्डिंग के लिए संदर्भ ढहना आवश्यक है (जिस पर चर्चा नहीं की जाएगी)।

टी एंड& एक रावल्यू संदर्भ नहीं है, लेकिन एक अग्रेषण संदर्भ है। यह भी lvalues ​​से बांधता है, जिस स्थिति में T और T&& एंड दोनों दोनों lvalue संदर्भ हैं।

यदि आप राक्षसों के लिए फ़ंक्शन टेम्पलेट को बाधित करना चाहते हैं, तो आप प्रकार के लक्षणों के साथ SFINAE को जोड़ सकते हैं:

#include <type_traits>

template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type
foo(T&&);

कदम का कार्यान्वयन

अब जब आप संदर्भ ढहने को समझते हैं, तो यहां बताया गया है कि std::move कैसे लागू किया गया है:

template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
    return static_cast<typename std::remove_reference<T>::type&&>(t);
}

जैसा कि आप देख सकते हैं, अग्रेषण संदर्भ T&& किसी भी प्रकार के पैरामीटर को स्वीकार करते हैं, और यह एक रैल्यू संदर्भ देता है। std::remove_reference<T>::type मेटा-फ़ंक्शन कॉल आवश्यक है क्योंकि अन्यथा, प्रकार X अंतराल के लिए, रिटर्न प्रकार X& && होगा, जो X& में गिर जाएगा। चूंकि t हमेशा एक अंतराल होता है (याद रखें कि एक नामित रावल्यू संदर्भ एक अंतराल है), लेकिन हम t को एक रावल्यू संदर्भ में बांधना चाहते हैं, हमें स्पष्ट रूप से सही रिटर्न प्रकार में t डालना होगा। एक फ़ंक्शन का कॉल जो एक रावल्यू संदर्भ देता है वह स्वयं एक xvalue है। अब आप जानते हैं कि कहां से आते हैं;)

एक फ़ंक्शन का कॉल जो एक रावल्यू संदर्भ देता है, जैसे कि std::move , एक xvalue है।

ध्यान दें कि rvalue संदर्भ द्वारा लौटने से इस उदाहरण में ठीक है, क्योंकि t स्वचालित ऑब्जेक्ट को इंगित नहीं करता है, बल्कि इसके बजाय कॉलर द्वारा पारित एक ऑब्जेक्ट।


उदाहरण कोड के साथ चाल semantics समझने के लिए मुझे सबसे आसान लगता है। आइए एक बहुत ही सरल स्ट्रिंग क्लास से शुरू करें जो केवल स्मृति के एक ढेर-आवंटित ब्लॉक के लिए सूचक होता है:

#include <cstring>
#include <algorithm>

class string
{
    char* data;

public:

    string(const char* p)
    {
        size_t size = strlen(p) + 1;
        data = new char[size];
        memcpy(data, p, size);
    }

चूंकि हमने खुद को स्मृति का प्रबंधन करना चुना है, इसलिए हमें तीन के नियमों का पालन ​​करना होगा। मैं असाइनमेंट ऑपरेटर लिखने को रोकता हूं और केवल विनाशक और कॉपी कन्स्ट्रक्टर को अभी लागू करता हूं:

    ~string()
    {
        delete[] data;
    }

    string(const string& that)
    {
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }

कॉपी कन्स्ट्रक्टर परिभाषित करता है कि स्ट्रिंग ऑब्जेक्ट्स की प्रतिलिपि बनाने का क्या अर्थ है। पैरामीटर const string& that टाइप स्ट्रिंग के सभी अभिव्यक्तियों से जुड़ा हुआ है जो आपको निम्न उदाहरणों में प्रतिलिपि बनाने की अनुमति देता है:

string a(x);                                    // Line 1
string b(x + y);                                // Line 2
string c(some_function_returning_a_string());   // Line 3

अब semantics चलने में महत्वपूर्ण अंतर्दृष्टि आता है। ध्यान दें कि केवल पहली पंक्ति में जहां हम x की प्रतिलिपि बनाते हैं, यह गहरी प्रति वास्तव में जरूरी है, क्योंकि हम बाद में x का निरीक्षण करना चाहेंगे और अगर कोई x बदल गया है तो बहुत आश्चर्यचकित होगा। क्या आपने देखा कि मैंने x तीन बार कैसे कहा (चार बार यदि आप इस वाक्य को शामिल करते हैं) और हर बार सही वस्तु का मतलब था? हम x "lvalues" जैसे अभिव्यक्तियों को बुलाते हैं।

रेखा 2 और 3 में तर्क लालसा नहीं हैं, लेकिन राजस्व हैं, क्योंकि अंतर्निहित स्ट्रिंग ऑब्जेक्ट्स के पास कोई नाम नहीं है, इसलिए क्लाइंट के पास बाद में किसी बिंदु पर उनका निरीक्षण करने का कोई तरीका नहीं है। राजस्व अस्थायी वस्तुओं को दर्शाता है जो अगले अर्धविराम में नष्ट हो जाते हैं (अधिक सटीक होने के लिए: पूर्ण अभिव्यक्ति के अंत में जो लंबवत रूप से रावल्यू होता है)। यह महत्वपूर्ण है क्योंकि b और c के प्रारंभ के दौरान, हम स्रोत स्ट्रिंग के साथ जो कुछ भी चाहते थे, हम कर सकते थे, और ग्राहक एक अंतर नहीं बता सका !

सी ++ 0x "रावल्यू रेफरेंस" नामक एक नई तंत्र पेश करता है, जो अन्य चीजों के साथ, हमें फ़ंक्शन ओवरलोडिंग के माध्यम से रैवल्यू तर्कों का पता लगाने की अनुमति देता है। हमें बस एक रैल्यू संदर्भ पैरामीटर के साथ एक कन्स्ट्रक्टर लिखना है। उस कन्स्ट्रक्टर के अंदर हम स्रोत के साथ कुछ भी कर सकते हैं, जब तक हम इसे किसी वैध स्थिति में छोड़ दें:

    string(string&& that)   // string&& is an rvalue reference to a string
    {
        data = that.data;
        that.data = nullptr;
    }

हमने यहाँ क्या किया है? ढेर डेटा को गहराई से कॉपी करने के बजाय, हमने अभी पॉइंटर की प्रतिलिपि बनाई है और फिर मूल पॉइंटर को शून्य पर सेट कर दिया है। असल में, हमने उस डेटा को "चोरी" किया है जो मूल रूप से स्रोत स्ट्रिंग से संबंधित था। फिर, मुख्य अंतर्दृष्टि यह है कि किसी भी परिस्थिति में ग्राहक को पता नहीं लगा कि स्रोत संशोधित किया गया था। चूंकि हम वास्तव में यहां एक प्रतिलिपि नहीं करते हैं, इसलिए हम इस कन्स्ट्रक्टर को "चालक कन्स्ट्रक्टर" कहते हैं। इसका काम संसाधनों को प्रतिलिपि बनाने के बजाय एक ऑब्जेक्ट से दूसरे स्थान पर ले जाना है।

बधाई हो, अब आप चलती शब्दावली की मूल बातें समझते हैं! आइए असाइनमेंट ऑपरेटर को कार्यान्वित करके जारी रखें। यदि आप प्रतिलिपि से स्वैच्छिक हैं और मुहावरे को स्वैप कर रहे हैं, तो इसे सीखें और वापस आएं, क्योंकि यह अपवाद सुरक्षा से संबंधित एक शानदार सी ++ मुहावरे है।

    string& operator=(string that)
    {
        std::swap(data, that.data);
        return *this;
    }
};

हू, क्या यह है? "रावल्यू संदर्भ कहां है?" आप पूछ सकते हैं "हमें इसकी आवश्यकता नहीं है!" मेरा जवाब है :)

ध्यान दें कि हम उस पैरामीटर को मानते हैं that मूल्य से है , इसलिए इसे किसी अन्य स्ट्रिंग ऑब्जेक्ट की तरह प्रारंभ करना होगा। वास्तव में यह कैसे शुरू किया जा रहा है? C++98 के पुराने दिनों में, जवाब "कॉपी कन्स्ट्रक्टर द्वारा" होता। सी ++ 0x में, संकलक कॉपी कन्स्ट्रक्टर और चालक कन्स्ट्रक्टर के बीच चुनता है कि इस पर आधारित है कि असाइनमेंट ऑपरेटर का तर्क एक लवल्यू या रावल्यू है या नहीं।

तो यदि आप a = b कहते हैं, तो कॉपी कन्स्ट्रक्टर प्रारंभ करेगा (क्योंकि अभिव्यक्ति b एक लवल्यू है), और असाइनमेंट ऑपरेटर सामग्री को ताजा बनाई गई, गहरी प्रतिलिपि के साथ बदल देता है। कॉपी और स्वैप मुहावरे की यह बहुत परिभाषा है - प्रतिलिपि बनाएं, प्रतिलिपि के साथ सामग्री को स्वैप करें, और फिर दायरे को छोड़कर प्रतिलिपि से छुटकारा पाएं। यहां कुछ भी नया नहीं है।

लेकिन यदि आप a = x + y कहते हैं, तो चालक कन्स्ट्रक्टर प्रारंभ करेगा (क्योंकि अभिव्यक्ति x + y एक रावल्यू है), इसलिए इसमें कोई गहरी प्रति शामिल नहीं है, केवल एक कुशल चाल है। यह अभी भी तर्क से एक स्वतंत्र वस्तु है, लेकिन इसका निर्माण छोटा था, चूंकि ढेर डेटा की प्रतिलिपि बनाने की आवश्यकता नहीं थी, बस चले गए। इसे कॉपी करने के लिए जरूरी नहीं था क्योंकि x + y एक रैल्यू है, और फिर, रैल्यू द्वारा निर्दिष्ट स्ट्रिंग ऑब्जेक्ट्स से स्थानांतरित करना ठीक है।

सारांशित करने के लिए, कॉपी कन्स्ट्रक्टर एक गहरी प्रतिलिपि बनाता है, क्योंकि स्रोत छूटे रहना चाहिए। दूसरी ओर, चालक कन्स्ट्रक्टर, पॉइंटर की प्रतिलिपि बना सकता है और उसके बाद पॉइंटर को स्रोत में शून्य पर सेट कर सकता है। स्रोत ऑब्जेक्ट को इस तरह से "शून्य" करना ठीक है, क्योंकि क्लाइंट के पास ऑब्जेक्ट का फिर से निरीक्षण करने का कोई तरीका नहीं है।

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


You know what a copy semantics means right? it means you have types which are copyable, for user-defined types you define this either buy explicitly writing a copy constructor & assignment operator or the compiler generates them implicitly. This will do a copy.

Move semantics is basically a user-defined type with constructor that takes an r-value reference (new type of reference using && (yes two ampersands)) which is non-const, this is called a move constructor, same goes for assignment operator. So what does a move constructor do, well instead of copying memory from it's source argument it 'moves' memory from the source to the destination.

When would you want to do that? well std::vector is an example, say you created a temporary std::vector and you return it from a function say:

std::vector<foo> get_foos();

You're going to have overhead from the copy constructor when the function returns, if (and it will in C++0x) std::vector has a move constructor instead of copying it can just set it's pointers and 'move' dynamically allocated memory to the new instance. It's kind of like transfer-of-ownership semantics with std::auto_ptr.


If you are really interested in a good, in-depth explanation of move semantics, I'd highly recommend reading the original paper on them, "A Proposal to Add Move Semantics Support to the C++ Language."

It's very accessible and easy to read and it makes an excellent case for the benefits that they offer. There are other more recent and up to date papers about move semantics available on the WG21 website , but this one is probably the most straightforward since it approaches things from a top-level view and doesn't get very much into the gritty language details.


In easy (practical) terms:

Copying an object means copying its "static" members and calling the new operator for its dynamic objects. सही?

class A
{
   int i, *p;

public:
   A(const A& a) : i(a.i), p(new int(*a.p)) {}
   ~A() { delete p; }
};

However, to move an object (I repeat, in a practical point of view) implies only to copy the pointers of dynamic objects, and not to create new ones.

But, is that not dangerous? Of course, you could destruct a dynamic object twice (segmentation fault). So, to avoid that, you should "invalidate" the source pointers to avoid destructing them twice:

class A
{
   int i, *p;

public:
   // Movement of an object inside a copy constructor.
   A(const A& a) : i(a.i), p(a.p)
   {
     a.p = nullptr; // pointer invalidated.
   }

   ~A() { delete p; }
   // Deleting NULL, 0 or nullptr (address 0x0) is safe. 
};

Ok, but if I move an object, the source object becomes useless, no? Of course, but in certain situations that's very useful. The most evident one is when I call a function with an anonymous object (temporal, rvalue object, ..., you can call it with different names):

void heavyFunction(HeavyType());

In that situation, an anonymous object is created, next copied to the function parameter, and afterwards deleted. So, here it is better to move the object, because you don't need the anonymous object and you can save time and memory.

This leads to the concept of an "rvalue" reference. They exist in C++11 only to detect if the received object is anonymous or not. I think you do already know that an "lvalue" is an assignable entity (the left part of the = operator), so you need a named reference to an object to be capable to act as an lvalue. A rvalue is exactly the opposite, an object with no named references. Because of that, anonymous object and rvalue are synonyms. इसलिए:

class A
{
   int i, *p;

public:
   // Copy
   A(const A& a) : i(a.i), p(new int(*a.p)) {}

   // Movement (&& means "rvalue reference to")
   A(A&& a) : i(a.i), p(a.p)
   {
      a.p = nullptr;
   }

   ~A() { delete p; }
};

In this case, when an object of type A should be "copied", the compiler creates a lvalue reference or a rvalue reference according to if the passed object is named or not. When not, your move-constructor is called and you know the object is temporal and you can move its dynamic objects instead of copying them, saving space and memory.

It is important to remember that "static" objects are always copied. There's no ways to "move" a static object (object in stack and not on heap). So, the distinction "move"/ "copy" when an object has no dynamic members (directly or indirectly) is irrelevant.

If your object is complex and the destructor has other secondary effects, like calling to a library's function, calling to other global functions or whatever it is, perhaps is better to signal a movement with a flag:

class Heavy
{
   bool b_moved;
   // staff

public:
   A(const A& a) { /* definition */ }
   A(A&& a) : // initialization list
   {
      a.b_moved = true;
   }

   ~A() { if (!b_moved) /* destruct object */ }
};

So, your code is shorter (you don't need to do a nullptr assignment for each dynamic member) and more general.

Other typical question: what is the difference between A&& and const A&& ? Of course, in the first case, you can modify the object and in the second not, but, practical meaning? In the second case, you can't modify it, so you have no ways to invalidate the object (except with a mutable flag or something like that), and there is no practical difference to a copy constructor.

And what is perfect forwarding ? It is important to know that a "rvalue reference" is a reference to a named object in the "caller's scope". But in the actual scope, a rvalue reference is a name to an object, so, it acts as a named object. If you pass an rvalue reference to another function, you are passing a named object, so, the object isn't received like a temporal object.

void some_function(A&& a)
{
   other_function(a);
}

The object a would be copied to the actual parameter of other_function . If you want the object a continues being treated as a temporary object, you should use the std::move function:

other_function(std::move(a));

With this line, std::move will cast a to an rvalue and other_function will receive the object as a unnamed object. Of course, if other_function has not specific overloading to work with unnamed objects, this distinction is not important.

Is that perfect forwarding? Not, but we are very close. Perfect forwarding is only useful to work with templates, with the purpose to say: if I need to pass an object to another function, I need that if I receive a named object, the object is passed as a named object, and when not, I want to pass it like a unnamed object:

template<typename T>
void some_function(T&& a)
{
   other_function(std::forward<T>(a));
}

That's the signature of a prototypical function that uses perfect forwarding, implemented in C++11 by means of std::forward . This function exploits some rules of template instantiation:

 `A& && == A&`
 `A&& && == A&&`

So, if T is a lvalue reference to A ( T = A&), a also ( A& && => A&). If T is a rvalue reference to A , a also (A&& && => A&&). In both cases, a is a named object in the actual scope, but T contains the information of its "reference type" from the caller scope's point of view. This information ( T ) is passed as template parameter to forward and 'a' is moved or not according to the type of T .


It's like copy semantics, but instead of having to duplicate all of the data you get to steal the data from the object being "moved" from.


Move semantics is about transferring resources rather than copying them when nobody needs the source value anymore.

In C++03, objects are often copied, only to be destroyed or assigned-over before any code uses the value again. For example, when you return by value from a function—unless RVO kicks in—the value you're returning is copied to the caller's stack frame, and then it goes out of scope and is destroyed. This is just one of many examples: see pass-by-value when the source object is a temporary, algorithms like sort that just rearrange items, reallocation in vector when its capacity() is exceeded, etc.

When such copy/destroy pairs are expensive, it's typically because the object owns some heavyweight resource. For example, vector<string> may own a dynamically-allocated memory block containing an array of string objects, each with its own dynamic memory. Copying such an object is costly: you have to allocate new memory for each dynamically-allocated blocks in the source, and copy all the values across. Then you need deallocate all that memory you just copied. However, moving a large vector<string> means just copying a few pointers (that refer to the dynamic memory block) to the destination and zeroing them out in the source.





move-semantics