【问题标题】:Returning std::vector<std::unique_ptr<T>> from function从函数返回 std::vector<std::unique_ptr<T>>
【发布时间】:2022-10-21 13:30:08
【问题描述】:

我试图从一个函数返回一个vector&lt;unique_ptr&gt;,但我遇到了错误。我正在使用 MSVC 19.33.31630。

this question 的 cmets 建议按值返回,但此代码:

std::vector<std::unique_ptr<int>> test1()
{
    std::vector<std::unique_ptr<int>> ret = { std::make_unique<int>(1) };
    return ret;
}

int main()
{
    std::vector<std::unique_ptr<int>> x = test1();
    std::unique_ptr<int>& ptrY = x.at(0);
    return 0;
}

产生此错误:

错误 C2280 'std::unique_ptr<int,std::default_delete<int>>::unique_ptr(const std::unique_ptr<int,std::default_delete<int>> &)':试图引用已删除的函数

通过引用返回,如下代码:

std::vector<std::unique_ptr<int>>& test2()
{
    std::vector<std::unique_ptr<int>> ret = { std::make_unique<int>(1) };
    return ret;
}

int main()
{
    std::vector<std::unique_ptr<int>>& y = test2();
    std::cout << *(y.at(0)) << std::endl; 

    return 0;
}

产生相同的错误。

为什么会这样? unique_ptr 的所有权是否没有正确转移?

【问题讨论】:

  • 有趣的事实:这是不返回任何内容的代码:godbolt.org/z/zo1WWseMz unique_ptr 被复制到 initializer_list 中,这就是复制的位置。
  • 避免复制,所以使用 return std::make_unique&lt;T&gt;()auto ptr = std::make_unique&lt;T&gt;(); .. do something with p .. return p; 如果你愿意,你可以从测试 2 中返回 auto
  • test2 只要通过引用返回就注定会遭遇不幸。返回对局部变量的引用是不可能的。据报道,test1test2 有相同的错误信息,这让我更仔细地研究了它。通过引用返回没有返回副本,因此如果出现相同的错误,则副本必须在其他地方。
  • 寻找一个好的副本brought this question up。作为一个骗子,我不愿意关闭,因为这没有给出答案,但我没有看到答案。即使您将move 复制到initializer_listinitializer_list 仍然会被复制到vector。您可能会遇到emplace_backs 的块。
  • 这看起来像是一个可行的副本(但我可能会有偏见,因为它回答了我关于为什么你不能搬出 initializer_list 的问题 - DUH!它是const):Any way to initialize a vector of unique_ptr?

标签: c++ c++11 stl


【解决方案1】:

感谢 @user4581301 在 cmets 中回答这个问题。 据我所知:

  • test2() 将 std::vector 放在超出范围的堆栈上。
  • test1() 最终将 unique_ptr 复制到 initializer_list

解决办法是按值返回,用 push_back/emplace_back 代替 initializer_list 来初始化向量:

std::vector<std::unique_ptr<int>> test3()
{
    std::vector<std::unique_ptr<int>> ret;
    ret.push_back(std::make_unique<int>(1));
    return ret;
}

int main()
{
    std::vector<std::unique_ptr<int>> x = test3();
    std::unique_ptr<int>& ptrY = x.at(0); 
    std::cout << *ptrY;

    return 0;
}

【讨论】:

  • 我错了。 unique_ptr 没有被复制到 initializer_list 中。我们可以用std::vector&lt;std::unique_ptr&lt;int&gt;&gt; ret = { std::move( std::make_unique&lt;int&gt;(1) ) }; 解决这个问题。真正的反派是std::vector 不能move initializer_list 中的项目,因为initializer_list 中的项目是const
【解决方案2】:
std::vector<std::unique_ptr<int>> x = test1();

您正在制作test1 在此处返回的向量的副本,这反过来又需要复制其所有内容。这就是赋值运算符= 默认所做的!只有在复制完成后,test1 的返回值才会超出范围并被破坏。

您正在尝试复制unique_ptrs;无法复制 unique_ptr。所以,这是正确的。

使用std::move 移动向量所持有的值。

更好的是,不要使用赋值运算符,而是直接从返回值构造,这将使用移动构造函数(如果函数按值返回)。

【讨论】:

  • 那不是赋值运算符
  • 嗯,= @NeilButterworth 还有什么?
  • @MarcusMüller——这是这个上下文中的复制构造函数。
  • @MarcusMüller 当= 用于变量声明时,即type var = value;,它只是语法糖,该操作实际上调用了复制构造,而不是赋值。它相当于type var(value); 在现代C++ 中,当值来自函数调用的返回值时,您描述的副本被优化掉,因此函数直接初始化变量。
  • 问题的位置不在于获取返回值的位置,而在于函数内部的向量构造。
猜你喜欢
  • 2018-02-02
  • 2019-11-07
  • 2015-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多