Search code examples
c++c++17shared-ptrunique-ptr

Unwanted copy constructor call when creating a shared_ptr


While doing some coding practice, I encountered the following error when attempting to pass ownership of an optional std::unique_ptr to a std::shared_ptr:

/usr/include/c++/11/ext/new_allocator.h:162:11: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = unsigned char; _Dp = std::default_delete]’
  162 |         { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
      |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/11/memory:76,
                 from main.cpp:2:
/usr/include/c++/11/bits/unique_ptr.h:468:7: note: declared here
  468 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~

This was the particular snippet that resulted in my error (apologies for the nonsensical code):

#include <optional>
#include <memory>

class test_class
{
    public:
        struct options
        {
            options();
            std::optional<std::unique_ptr<uint8_t>> data;
        };
    
        test_class(const test_class::options &options = test_class::options());

    private:
        std::shared_ptr<std::unique_ptr<uint8_t>> _data;
};

test_class::test_class(
    const test_class::options &options)
{
    if (options.data.has_value())
    {
        _data = std::make_shared<std::unique_ptr<uint8_t>>(std::move(options.data.value()));
    }
}

I assumed that I would've bypassed any copy constructors by using the std::move call, but it seems like I may be inadvertently calling it anyway.

Furthermore, I ran this similar snippet of code, but there was no error:

test_class::test_class(
    const test_class::options &options)
{
    std::optional<std::unique_ptr<uint8_t>> data;

    if (data.has_value())
    {
        _data = std::make_shared<std::unique_ptr<uint8_t>>(std::move(data.value()));
    }
}

Can anyone explain why this is happening? Why is the first snippet wrong? Is there some C++ fundamental I am misunderstanding? Why does the second snippet not also fail?

Thanks!


Solution

  • As stated in a comment above, this behavior is a result of the test_class::options argument of the constructor being const. The use of std::move is intended to cast options.data.value to a const std::unique_ptr<uint8_t>&&. However, due to the const qualifier, it prevents the use of the move constructor. Consequently, the code attempts to construct a new unique_ptr from this casted value but falls back to using the copy constructor instead of the move constructor.

    I corrected this as follows:

    #include <optional>
    #include <memory>
    
    class test_class
    {
        public:
            struct options
            {
                options();
                
                std::optional<std::unique_ptr<uint8_t>> data;
            };
            
            test_class(test_class::options &&options =  = dst_file::options());
    
        private:
            std::shared_ptr<std::unique_ptr<uint8_t>> _data;
    };
    
    test_class::test_class(
        const test_class::options &options)
    {
       
    }
    
    test_class::test_class(
        test_class::options &&options)
    {
        if (options.data.has_value())
        {
            _data = std::make_shared<std::unique_ptr<uint8_t>>(std::move(options.data.value()));
        }
    }
    

    Since test_class::options is now an "rvalue reference" (as denoted by the &&), it will invoke the move constructor by default.