Search

15.1 — Intro to smart pointers and move semantics

Consider a function in which we dynamically allocate a value:

Although the above code seems fairly straightforward, it’s fairly easy to forget to deallocate ptr. Even if you do remember to delete ptr at the end of the function, there are a myriad of ways that ptr may not be deleted if the function exits early. This can happen via an early return:

or via a thrown exception:

In the above two programs, the early return or throw statement execute, causing the function to terminate without variable ptr being deleted. Consequently, the memory allocated for variable ptr is now leaked (and will be leaked again every time this function is called and returns early).

At heart, these kinds of issues occur because pointer variables have no inherent mechanism to clean up after themselves.

Smart pointer classes to the rescue?

One of the best things about classes is that they contain destructors that automatically get executed when an object of the class goes out of scope. So if you allocate (or acquire) memory in your constructor, you can deallocate it in your destructor, and be guaranteed that the memory will be deallocated when the class object is destroyed (regardless of whether it goes out of scope, gets explicitly deleted, etc…). This is at the heart of the RAII programming paradigm that we talked about in lesson 8.7 -- Destructors.

So can we use a class to help us manage and clean up our pointers? We can!

Consider a class whose sole job was to hold and “own” a pointer passed to it, and then deallocate that pointer when the class object went out of scope. As long as objects of that class were only created as local variables, we could guarantee that the class would properly go out of scope (regardless of when or how our functions terminate) and the owned pointer would get destroyed.

Here’s a first draft of the idea:

This program prints:

Resource acquired
Resource destroyed

Consider how this program and class work. First, we dynamically create a Resource, and pass it as a parameter to our templated Auto_ptr1 class. From that point forward, our Auto_ptr1 variable res owns that Resource object (Auto_ptr1 has a composition relationship with m_ptr). Because res is declared as a local variable and has block scope, it will go out of scope when the block ends, and be destroyed (no worries about forgetting to deallocate it). And because it is a class, when it is destroyed, the Auto_ptr1 destructor will be called. That destructor will ensure that the Resource pointer it is holding gets deleted!

As long as Auto_ptr1 is defined as a local variable (with automatic duration, hence the “Auto” part of the class name), the Resource will be guaranteed to be destroyed at the end of the block it is declared in, regardless of how the function terminates (even if it terminates early).

Such a class is called a smart pointer. A Smart pointer is a composition class that is designed to manage dynamically allocated memory and ensure that memory gets deleted when the smart pointer object goes out of scope. (Relatedly, built-in pointers are sometimes called “dumb pointers” because they can’t clean up after themselves).

Now let’s go back to our someFunction() example above, and show how a smart pointer class can solve our challenge:

If the user enters a non-zero integer, the above program will print:

Resource acquired
Hi!
Resource destroyed

If the user enters zero, the above program will terminate early, printing:

Resource acquired
Resource destroyed

Note that even in the case where the user enters zero and the function terminates early, the Resource is still properly deallocated.

Because the ptr variable is a local variable, ptr will be destroyed when the function terminates (regardless of how it terminates). And because the Auto_ptr1 destructor will clean up the Resource, we are assured that the Resource will be properly cleaned up.

A critical flaw

The Auto_ptr1 class has a critical flaw lurking behind some auto-generated code. Before reading further, see if you can identify what it is. We’ll wait…

(Hint: consider what parts of a class get auto-generated if you don’t supply them)

(Jeopardy music)

Okay, time’s up.

Rather than tell you, we’ll show you. Consider the following program:

This program prints:

Resource acquired
Resource destroyed
Resource destroyed

Very likely (but not necessarily) your program will crash at this point. See the problem now? Because we haven’t supplied a copy constructor or an assignment operator, C++ provides one for us. And the functions it provides do shallow copies. So when we initialize res2 with res1, both Auto_ptr1 variables are pointed at the same Resource. When res2 goes out of the scope, it deletes the resource, leaving res1 with a dangling pointer. When res1 goes to delete its (already deleted) Resource, crash!

You’d run into a similar problem with a function like this:

In this program, res1 will be copied by value into passByValue’s parameter res, leading to duplication of the Resource pointer. Crash!

So clearly this isn’t good. How can we address this?

Well, one thing we could do would be to explicitly define and delete the copy constructor and assignment operator, thereby preventing any copies from being made in the first place. That would prevent the pass by value case (which is good, we probably shouldn’t be passing these by value anyway).

But then how would we return an Auto_ptr1 from a function back to the caller?

We can’t return our Auto_ptr1 by reference, because the local Auto_ptr1 will be destroyed at the end of the function, and the caller will be left with a dangling reference. Return by address has the same problem. We could return pointer r by address, but then we might forget to delete r later, which is the whole point of using smart pointers in the first place. So that’s out. Returning the Auto_ptr1 by value is the only option that makes sense -- but then we end up with shallow copies, duplicated pointers, and crashes.

Another option would be to override the copy constructor and assignment operator to make deep copies. In this way, we’d at least guarantee to avoid duplicate pointers to the same object. But copying can be expensive (and may not be desirable or even possible), and we don’t want to make needless copies of objects just to return an Auto_ptr1 from a function. Plus assigning or initializing a dumb pointer doesn’t copy the object being pointed to, so why would we expect smart pointers to behave differently?

What do we do?

Move semantics

What if, instead of having our copy constructor and assignment operator copy the pointer (“copy semantics”), we instead transfer/move ownership of the pointer from the source to the destination object? This is the core idea behind move semantics. Move semantics means the class will transfer ownership of the object rather than making a copy.

Let’s update our Auto_ptr1 class to show how this can be done:

This program prints:

Resource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed

Note that our overloaded operator= gave ownership of m_ptr from res1 to res2! Consequently, we don’t end up with duplicate copies of the pointer, and everything gets tidily cleaned up.

std::auto_ptr, and why to avoid it

Now would be an appropriate time to talk about std::auto_ptr. std::auto_ptr, introduced in C++98, was C++’s first attempt at a standardized smart pointer. std::auto_ptr opted to implement move semantics just like the Auto_ptr2 class does.

However, std::auto_ptr (and our Auto_ptr2 class) has a number of problems that makes using it dangerous.

First, because std::auto_ptr implements move semantics through the copy constructor and assignment operator, passing a std::auto_ptr by value to a function will cause your resource to get moved to the function parameter (and be destroyed at the end of the function when the function parameters go out of scope). Then when you go to access your auto_ptr argument from the caller (not realizing it was transferred and deleted), you’re suddenly dereferencing a null pointer. Crash!

Second, std::auto_ptr always deletes its contents using non-array delete. This means auto_ptr won’t work correctly with dynamically allocated arrays, because it uses the wrong kind of deallocation. Worse, it won’t prevent you from passing it a dynamic array, which it will then mismanage, leading to memory leaks.

Finally, auto_ptr doesn’t play nice with a lot of the other classes in the standard library, including most of the containers and algorithms. This occurs because those standard library classes assume that when they copy an item, it actually makes a copy, not does a move.

Because of the above mentioned shortcomings, std::auto_ptr has been deprecated in C++11, and it should not be used. In fact, std::auto_ptr is slated for complete removal from the standard library as part of C++17!

Rule: std::auto_ptr is deprecated and should not be used. (Use std::unique_ptr or std::shared_ptr instead)..

Moving forward

The core problem with the design of std::auto_ptr is that prior to C++11, the C++ language simply had no mechanism to differentiate “copy semantics” from “move semantics”. Overriding the copy semantics to implement move semantics leads to weird edge cases and inadvertent bugs. For example, you can write res1 = res2 and have no idea whether res2 will be changed or not!

Because of this, in C++11, the concept of “move” was formally defined, and “move semantics” were added to the language to properly differentiate copying from moving. Now that we’ve set the stage for why move semantics can be useful, we’ll explore the topic of move semantics throughout the rest of this chapter. We’ll also fix our Auto_ptr2 class using move semantics.

In C++11, std::auto_ptr has been replaced by a bunch of other types of “move-aware” smart pointers: std::scoped_ptr, std::unique_ptr, std::weak_ptr, and std::shared_ptr. We’ll also explore the two most popular of these: unique_ptr (which is a direct replacement for auto_ptr) and shared_ptr.

15.2 -- R-value references
Index
14.x -- Chapter 14 comprehensive quiz

119 comments to 15.1 — Intro to smart pointers and move semantics

  • Parsa

    The sayHi() function prints "Hi" then new line. In the output example it printed "Hi!" then new line

  • Parsa

    The operator-> overload seems a bit confusing. Was there a tutorial on it that I missed?

    Nevermind. I think I understand.

  • cdecde57

    Hello! BTW the lessons have been great! I have been learning from this place sense I was interested in c++ and really this is the only place I have learned things. It is amazing and thanks!

    Anyways I am curious about the smart pointer class we made.

    I understand how it works and how it dealocates itself. At the end of this lesson it talks about things like
    std::unique_ptr and I have found that it is very similar to the class were making. Are we making the class to really explore the idea of move semantics and really whats going on behind things like std::unique_ptr or is the class something completely different?

    I guess what I am asking is do I need to memorize how to create this class and everything, say because there is not one that works similarly to it in the standard library or do we just need to learn it and understand it so we can better understand pointers and the semantics of what you can actually do with them when implemented into classes and things like that in general?

    Thanks!

    • Like a lot of things, this class can (and should be) replaced with something from the standard library (In this case, a smart pointer from <memory>).
      It's important to understand how such a class can be implemented by hand, because you'll have to do it at some point (Not necessarily an existing class, but you need to know it for custom classes too).

    • Alex

      These classes are discussed to explore the ideas behind move semantics, starting from concepts we've already discussed (and why they don't work well in this case). This sets up the rationale for the new language mechanics that we discuss in subsequent lessons (e.g. rvalue references). You don't need to memorize these classes -- you should prefer the standard library versions instead (excluding std::auto_ptr).

  • Michael Song

    Hi!
    " We could return pointer r by address, but then we might forget to delete r later, which is the whole point of using smart pointers in the first place. "
    Was it meant to be "return pointer r by value"? Because if we return r by address, then r will go out of scope when the function ends and the address will be pointing to nothing.

    • Alex

      If we return the value pointer r points to by value, then r won't get deleted and we'll leak memory.

      • Michael Song

        Thanks, Alex. I can get what you mean now.
        What if we don't return the value the pointer points to by value but return the pointer itself by value instead?
        Is returning the pointer itself by value same as returning the value the pointer points to by address?
        The pointer r holds the address of the memory for Resource as its value and if we return that to another pointer. That pointer will point to the memory for Resource and r can safely go out of scope without a memory leak.
        Does it work this way?

        • Alex

          Yes, returning the pointer itself by value is the same as returning the value the point points to by address.

          So yes, you could return r by address -- local variable r would go out of scope, but the address returned could be assigned to another pointer that's in scope for the caller.

          But then the caller is responsible for deleting the resource, which means we could end up with memory leaks. Fixing this is a big part of what the lessons in this chapter are about.

  • Azad

    Auto_Ptr2, line 19, 26 wont the type be written as Auto_ptr2<T>& instead of just Auto_Ptr2& ?

    • Hi!

      The template argument can be omitted, because the injected-class-name is the class name along with the class' template arguments.
      If you add the arguments to every use of `Auto_ptr2` inside the class, you'll have extra work if you ever decide to add or remove template parameters from the class later on.

      I don't think @Alex covered this.

  • X

    Can you explain why there is a const keyword in these statements? I've seen them twice in the same function stub and I don't understand why some of them come later in the statement (like these) rather than at the beginning. What is the difference between the two when you have two consts in the same stub and one comes before the T and one is in the spot it is at now? Basically, what I mean is something like "const T& operator*() const {return *m_ptr;}" I'm not sure if that is valid but I have seen you guys write code in that exact style and that is what I am confused about.

    And for the second piece of code in Main - why is the first line non-null? What makes it non-null? What value does Resource have? Also, what is <Resource> doing here? What is its purpose?

    • Lesson 8.10.

      • X

        Ohhh that makes a lot more sense thanks. Can you answer the second paragraph of questions for me as well?

        • `<Resource>` is a template argument, see Chapter 13.
          `new Resouce` allocates a new `Resource`, returns its pointer and passes it to `Auto_ptr2`'s constructor. Lesson 6.9, 8.5.

          • X

            Extremely helpful. Much appreciated.

          • X

            One last question on this topic.

            Is this the code that returns the pointer to be passed into the constructor?

  • Amrita

    Hi Alex
    Under the move semantics section, line 31 :

    delete m_ptr;

    I was expecting that this line will invoke the destructor of the Resource class. But it does not happen. Can you please explain?

    Regards
    Amrita

  • Anthony

    Am I correct in the saying that, really, the only difference in behaviour between 'default copy' and move semantics as far as the @Auto_ptr2 class is concerned, is just the assignment of the source pointer to nullptr (so that the memory to which it used to point won't be destructed when the source goes out of scope)?

      • Anthony

        Thanks :)

        Is it also true to say that the destructor associated with the object pointed to by Auto_ptr2's mptr will be triggered when Auto_ptr2 is itself destructed because it goes out of scope, even if we have 'moved' a different object into that pointer?

        • When a @Auto_ptr2 goes out of scope, it's destructor is fired. The destructor calls @delete on the currently managed object, causing its destructor to fire as well.
          If another pointer was moved into out @Auto_ptr2, then the old managed pointer will be deleted by @operator=, causing the old object to be destructed.
          If this isn't what you were asking, try posting an example of what you mean.

          • Anthony

            Hi,

            Yep, that's exactly what I was asking. I've summarized what I think is correct in the code comments below. Could you check what I've commented about the different versions of the assignment operator. I'm pretty sure it's all correct.

            Much appreciated - as always!

            • > The copy constructor is now a move constructor
              It's still a copy constructor, move constructors look different, they're covered later. But it behaves like a move constructor.

              > I still don't understand this
              @operator-> is special. C++ knows that it returns a pointer or reference to an object, and it will perform the member lookup for you. This isn't something you can do with regular functions or other operators.

              Everything else is correct :)

  • i don't understand how can "ptr->sayHi()" works with overloaded -> operator, it seems like it must be used twice so ptr-> first resolves and return address of res and then another arrow operator must be used res->sayHi() ?

    • Hi Michael!

      Good point.
      What you said is correct if we take the long way of calling @operator->

      But if we use @operator-> directly, we don't need another dereference or arrow.

      • Anthony

        I have exactly this problem. I don't get it :( Like Michael Stef says, it seems we must surely use the -> operator twice.

        When we overload the -> operator as described in this chapter:

        the expression resolves to (or returns, or is replaced by) the pointer. Then, to access any member of the Resource object, we should have to use this pointer with the -> operator in the normal manner. But now we're using the -> twice, which is not correct..

        Agh!

  • Yogesh

    I am trying to delete the ptr before assigning to new value in copy constructor(like assignment operator) , but doing that ,i am getting error.
    Could you please help me to root cause the error .

    • Alex

      Can you post your code? There isn't enough information here to determine what you might be doing incorrectly.

      • Yogesh

        template<typename T>
        class AutoPtr
        {
            T *ptr;
        public:
            AutoPtr(T *p=nullptr):ptr(p)
            {}
            ~AutoPtr()
            {
                delete ptr;
            }
            T& operator *()
            {
                return *ptr;
            }
            T* operator->()
            {
                return ptr;
            }
            AutoPtr( AutoPtr& source)
            {
                delete ptr;
                ptr = source.ptr;
                source.ptr = NULL;
            }
            AutoPtr& operator =(AutoPtr& source)
            {
                delete ptr;
                ptr = source.ptr;
                source.ptr = NULL;
                return *this;
            }
        };

        class Resource
        {
        public:
            Resource() { std::cout << "Resource acquired\n"; }
            ~Resource() { std::cout << "Resource destroyed\n"; }
        };

        int main()
        {
            AutoPtr<Resource> temp = new Resource();
            AutoPtr<Resource> temp1 = temp;
            getchar();
            getchar();
        }

      • Yogesh

        Hi Alex ,
        I posted the code .
        While calling the copy constructor , there was no existing value of ptr . But i am trying to delete that, which should not cause any problem (because compiler checks the pointer before deleting ), but i am getting run-time exception .

  • Danty Wong

    Hi,

    When res2 is declared, only a chunk of memory large enough for a Resource class object is set aside, so no constructor is called for creating res2.
    In the middle of the programme, the content of res1's m_ptr field is 'moved' to res2's m_ptr field. At what point does res2 gets its m_ptr field declared?

    At the end of the programme, only one line of "Resource destroyed" is printed. Is this from the destructor of res1 or res2? And if only one of the destructor is called, what about the other object in the block? Does it get destroyed as well?

    Thanks

    • Alex

      > When res2 is declared, only a chunk of memory large enough for a Resource class object is set aside, so no constructor is called for creating res2.

      No, when res2 is declared, an Auto_ptr2 is allocated, which contains a null pointer (m_ptr). No memory is set aside for a Resource object.

      When the move happens, the res1's m_ptr is transfered to res2's m_ptr. At this point, res1's m_ptr is set to null, as it no longer owns the object.

      At the end, the destructor is called from res2, as res2 is the owner of the resource.

      There is no other object in the block -- there's only one Resource (destroyed by res2) and two Auto_ptr2 that are automatically deallocated when the function ends.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]