【问题标题】:Efficiently and elegantly returning emplaced unique_ptr高效优雅地返回已放置的 unique_ptr
【发布时间】:2014-03-21 19:48:51
【问题描述】:

我发现 (thanks to a StackOverflow comment) 我的代码中有一个安全漏洞:

std::vector<std::unique_ptr<Item>> items;

template<class... TS> Item& create(TS&&... mArgs)
{
    auto item(new Item(std::forward<TS>(mArgs)...);
    items.emplace_back(item); // Possible exception and memory leak
    return *item;
}

基本上,如果emplace_back 抛出,则使用原始new 分配Item 可能会泄漏内存。

解决方案从不使用原始new,而是在方法主体中使用std::unique_ptr

std::vector<std::unique_ptr<Item>> items;

template<class... TS> Item& create(TS&&... mArgs)
{
    auto item(std::make_unique<Item>(std::forward<TS>(mArgs)...);
    items.emplace_back(std::move(item));
    return *item; // `item` was moved, this is invalid!
}

如您所见,返回 item 无效,因为我必须使用 std::move 移动 item 以将其放置到 items 容器中。

我想不出需要将item 的地址存储在附加变量中的解决方案。然而,原始(有缺陷的)解决方案非常简洁易读。

有没有更优雅的方式来返回被移动以放置在容器中的std::unique_ptr

【问题讨论】:

  • return *items.back();
  • 你为什么首先返回对 unique_ptr 内部的引用?
  • 如果items 向量突然消失了怎么办?
  • 顺便说一句,请注意代码只提供基本的异常保证:如果emplace_back 抛出,参数将已经被移出。
  • @avakar,有没有办法解决这个问题?

标签: c++ c++11 return unique-ptr emplace


【解决方案1】:

你可以写:

template<class... TS>
Item& create(TS&&... mArgs)
{
    items.emplace_back(std::make_unique<Item>(std::forward<TS>(mArgs)...));
    return *items.back();
}

【讨论】:

    【解决方案2】:

    在 emplace 之前缓存引用是一个更普遍适用的选项(例如,对于非向量容器):

    template<class... TS> Item& create(TS&&... mArgs)
    {
        auto item = std::make_unique<Item>(std::forward<TS>(mArgs)...);
        auto& foo = *item;
        items.emplace_back(std::move(item));
        return foo; // This *is* valid.
    }
    

    【讨论】:

    • 我认为如果 emplace 抛出,你仍然会遇到问题,andrej 最近有一篇文章,他建议先调用 .reserve()
    • @NoSenseEtAl 这基本上就是我们在 OP 的 cmets 中讨论的问题 - 尽管没有人真正说清楚。这足以提供基本的异常安全保证:如果emplace_back 抛出,作为右值传递给create 的参数将留在移出条件中,它们的内脏被撕掉并销毁。如果希望提供强大的异常安全保证,则应该在向量中保留空间items.reserve(items.size() + 1)其他任何事情发生之前,尤其是参数的潜在移动到unique_ptr
    • 更正:此函数无法提供强大的异常安全保证,因为无法确保Item构造函数成功或不修改mArgs .您可以保证 (a) 如果抛出异常,容器不会改变,并且 (b) 参数不会被修改,除非 Item 构造函数抛出。如果强保证很关键,则必须修改函数以接受 unique_ptr 参数并将其简单地移动到向量中。
    【解决方案3】:

    您的问题被标记为 C++11,而其他建议 make_unique 的答案没有提到它是 C++14 功能。我相信这种 C++11 方法也可以解决泄漏问题。

    #include <vector>
    #include <memory>
    #include <utility>
    #include <iostream>
    
    struct Item
    {
       int a, b;
       Item(int aa, int bb) : a{aa}, b{bb} { }
    };
    
    static std::vector<std::unique_ptr<Item>> items;
    
    template <class... Ts> Item& create(Ts&&... args)
    {
        items.emplace_back(std::unique_ptr<Item>{new Item(std::forward<Ts>(args)...)});
        return *items.back();
    }
    
    int main()
    {
        Item& x = create(1, 2);
        std::cout << "( " << x.a << ", " << x.b << " )" << std::endl;
    }
    

    这应该是安全的,因为在 unique_ptr&lt;Item&gt; 已经构建之前无法调用 emplace_back(),因此即使 emplace_back() 确实抛出,您的 Item 已经由 unique_ptr 管理。

    【讨论】:

    • 这个unique_ptr&lt;Item&gt;是在哪里构造的create?我没有看到它。你的意思是写items.emplace_back(std::unique_ptr&lt;Item&gt;{new Item(std::forward&lt;Ts&gt;(args)...)});
    • 我想我在想 unique_ptr&lt;Item&gt; 必须从 emplace_back 的参数中隐式构造,但我现在明白这是不对的。是的,您的建议看起来更好。
    猜你喜欢
    • 2010-10-21
    • 1970-01-01
    • 2017-04-29
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-18
    • 1970-01-01
    相关资源
    最近更新 更多