Search code examples
c++c++11initializationthread-safetyshared-ptr

Does shared_ptr a = make_shared() create a copy of the shared_ptr before constructor is run?


This might be a stupid question.

Let's say we're in C++11 land and we use make_shared() to create a smart pointer. We then use this smart pointer to initialize a variable with like this:

std::shared_ptr<class> = make_shared(/* args to c'tor of class*/ );

Now I know two things:

  1. Assignement is not initialization. In this case we have initialisation. This would mean in the above case probably the copy constructor is called for the shared_ptr which is returned by make_shared.
  2. Copy elision is only mandatory in C++17.

Does this mean that on every instance of make_shared a temporary copy of the shared_ptr is created and inserted into the copy constructor? Because this would mean for thread safety that a lock would have to be taken across the initialisation in case other threads preempt the thread and call shared_ptr::use_count() member function?


Solution

  • There are two things to avoid the copy:

    • 1 is the compiler's RVO (return value optimization);
    • 2 is the move constructor/assignment.

    for code auto foo = std::make_shared<Foo>(); RVO will create the object directly on the stack. and even we disable the RVO by -fno-elide-constructors, the move constructor will try used as the returned object from make_shared is a temporary one.

    Below is a simple test code. (this code only show the concept but not for a real-world shared_ptr implementation)

    #include <iostream>
    
    template <typename T>
    struct my_shared_ptr
    {
        T *t_{nullptr};
    
        my_shared_ptr(T *t): t_(t) {
            std::cout << "constructor" << std::endl;
        };
    
        my_shared_ptr(const my_shared_ptr<T>&) {
            std::cout << "copy" << std::endl;
        }
    
        my_shared_ptr<T>& operator=(const my_shared_ptr<T>&) {
            std::cout << "copy" << std::endl;
            return *this;
        }
    
    #ifndef NO_MOVE
        my_shared_ptr(my_shared_ptr<T>&&) {
            std::cout << "move" << std::endl;
        }
    
        my_shared_ptr<T>& operator=(my_shared_ptr<T>&&) {
            std::cout << "move" << std::endl;
            return *this;
        }
    #endif
    };
    
    template <typename T>
    my_shared_ptr<T>
    my_make_shared() {
        return my_shared_ptr<T>(new T);
    }
    
    struct Foo {};
    
    int main()
    {
        auto foo = my_make_shared<Foo>();
        return 0;
    }
    

    Condition 1, compile with c++11 shows:

    $ g++ a.cc -std=c++11 ; ./a.out
    constructor
    

    Condition 2, compile with c++11/disable RVO shows:

    $ g++ a.cc -std=c++11 -fno-elide-constructors ; ./a.out
    constructor
    move
    move
    

    Condition 3, compile with c++11/disable RVO/no move shows:

    $ g++ a.cc -std=c++11 -fno-elide-constructors -DNO_MOVE ; ./a.out
    constructor
    copy
    copy