创建shared_ptr时不需要的复制构造函数调用

Unwanted copy constructor call when creating a shared_ptr

提问人:Damian 提问时间:10/26/2023 最后编辑:Damian 更新时间:11/1/2023 访问量:151

问:

在进行一些编码练习时,我在尝试将可选的所有权传递给 :std::unique_ptrstd::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;
      |       ^~~~~~~~~~

这是导致我错误的特定片段(为无意义的代码道歉):

#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()));
    }
}

我以为我会通过使用调用绕过任何复制构造函数,但似乎我可能无意中调用了它。std::move

此外,我运行了这个类似的代码片段,但没有错误:

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()));
    }
}

谁能解释为什么会这样?为什么第一个片段是错误的?我是否误解了一些 C++ 基础知识?为什么第二个代码段不会也失败?

谢谢!

C++ C++17 共享 唯一 PTR

评论

2赞 Tony J 10/26/2023
您的代码不是将所有权从 unique_ptr传递到 shared_ptr,而是通过移动可选unique_ptr来创建uniqune_ptr的shared_ptr
1赞 BoP 10/26/2023
std::make_shared<std::unique_ptr<uint8_t>>会创建一个shared_ptr unique_ptr。完全没有意义......
0赞 BoP 10/26/2023
此外,我只会有一个持有 nullptr 的unique_ptr,而不是可选的unique_ptr。
7赞 n. m. could be an AI 10/26/2023
因为它是一个.您无法移出 .constconst
1赞 n. m. could be an AI 10/27/2023
@Peter-ReinstateMonica An 可以被复制,所以它只会依靠复制。 不可复制,这就是错误的原因。无法移动,也无法复制。intunique_ptr

答:

3赞 Damian 10/26/2023 #1

如上面的评论所述,此行为是构造函数的参数的结果。使用 的用途旨在转换为 .但是,由于 const 限定符,它阻止了 move 构造函数的使用。因此,代码尝试从此强制转换值构造新unique_ptr,但回退到使用复制构造函数而不是移动构造函数。test_class::optionsconststd::moveoptions.data.valueconst std::unique_ptr<uint8_t>&&

我更正如下:

#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()));
    }
}

由于现在是“右值引用”(由 表示),因此默认情况下它将调用移动构造函数。test_class::options&&

评论

1赞 Mooing Duck 10/27/2023
您能否编辑答案以澄清这一点:只是转换为 ,然后编写的代码尝试从中构造新的,但由于它不能使用移动构造函数,因此回退到复制构造函数。std::moveoptions.data.valueconst std::unique_ptr<uint8_t>&&unique_ptrconst
-1赞 Peter - Reinstate Monica 10/30/2023 #2

复杂的嵌套指针在某种程度上掩盖了潜在的问题。所有权在这里并不是真正的问题,从unique_ptr创建shared_ptr也没有问题。您得到的错误仅与unique_ptr有关。在这里,提到的“模糊”变得明显,因为错误很难理解。但是,如果我删除所有绒毛,例如 std:: 和模板参数,它归结为

使用已删除的函数 unique_ptr::unique_ptr(const unique_ptr &)

这是复制构造函数。删除unique_ptr的复制构造函数,因为无法复制unique_ptr;毕竟,它是独一无二的。

但是,为什么复制构造函数是通过重载分辨率来选择的呢?因为声明的 move 构造函数是从不是 const 的右值引用移动的。const rvalue 引用根本不匹配。然后,回退是尝试使用复制构造函数,因为 const 引用可以绑定到常量右值引用;但正如我们所说,删除这个是有充分理由的。

下面是演示简单类 S 工作原理的代码。你的构造函数案例是用 来演示的,但最简单的演示实际上是 中的最后一行: 你根本无法构造一个 from a const rref。an 可能是不同类的成员,并且构造是该类初始化的一部分,这种情况对问题并不重要。test_classmain()SS

#include <utility> // std::move

using namespace std;

// this takes on the role of unique_ptr in your example
struct S 
{ 
    S() = default;

    // [1] enable the copy constructor ... 
    //S(const S&) = default;

    // [2] ... or the move constructor from rref to const;
    //S(const S&&) {}

    // [3] OR remove the move constructor; that will un-delete the copy ctor
    S(S&&) = default;
};

class test_class
{
    public:
        S _s;
 
        // Does not work because no constructor matches. In the order of 
        // resolution attempts: 
        // 1. The best match, a constructor taking a const rref argument, is not declared, see [2]
        // 2. The declared regular move constructor [3] taking a non-const rref argument does not match;
        // 3. The regular copy constructor taking a const ref argument
        //    is implicitly deleted because we declared a move constructor [3].
        //    We could explicitly define it, [1]
        // test_class(const S &sArg): _s{move(sArg)} {}
 };

int main()
{
    const S cs;
     // move actually works like a charm but produces an rref to const.
    const S &&s_rref{move(cs)};

    // construction from a const rref is not defined.
    // S s{move(cs)};
}

这是 godbolt 上的一个版本。

评论

0赞 Peter - Reinstate Monica 11/21/2023
我很想知道我的答案的哪一部分对反对者来说似乎无用或错误。重读它,我仍然认为它解释了 OP 错误的根本原因。