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!
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.