为啥 make_unique to vector::back() 创建另一个副本?

Posted

技术标签:

【中文标题】为啥 make_unique to vector::back() 创建另一个副本?【英文标题】:Why does make_unique to vector::back() create another copy?为什么 make_unique to vector::back() 创建另一个副本? 【发布时间】:2019-09-30 07:40:27 【问题描述】:

这里复制一个简单的例子来演示我的问题

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>

struct Party 
    Party(std::string value) : value_(value) ;
    std::string value_;
;

int main() 
    std::unordered_map<std::string, std::unique_ptr<Party>> map_;
    std::vector<Party> parties_;

    parties_.emplace_back("AAA");
    parties_.emplace_back("BBB");
    parties_.emplace_back("CCC");

    std::unique_ptr<Party> ptr = std::make_unique<Party>(parties_.back());

    ptr->value_ = "XXX";

    for (auto& p : parties_) 
        std::cout << p.value_ << std::endl; // print: AAA\nBBB\nCCC
    


本质上,我想将对象存储在向量中,并让智能指针指向它们,并使用这些指针来修改向量对象的值。

但似乎在创建一个新的unique_ptrparties_.back() 实际上返回对象的副本而不是原始对象。

我如何在这里实现我想要的?谢谢!

【问题讨论】:

make_unique 中的“制造”是指创造(“让我成为披萨”),而不是转型(“让我成为百万富翁”)。 你为什么想要一个指针,无论是智能的还是其他的,指向已经是引用的东西? 我认为你应该只使用参考 【参考方案1】:

std::make_unique&lt;Party&gt;(something) 本质上等同于 std::unique_ptr&lt;Party&gt;(new Party(something)),所以是的,它正在创建 Party 对象的新副本,因为您要求它。

最终,unique_ptr 似乎不是正确的选择:如果您的 std::vector 已经是对象的唯一所有者(和生命周期的管理者),那么您不需要做任何特别的事情,您可以使用普通的指针/引用。但是请注意,它们的有效性与 std::vector 的引用失效规则相关 - 特别是,如果它决定重新分配(例如,如果您执行 push_back),所有指针/引用都将变为无效。

std::unordered_map<std::string, Party*> map_;
std::vector<Party> parties_;

parties_.emplace_back("AAA");
parties_.emplace_back("BBB");
parties_.emplace_back("CCC");

Party *ptr = &parties_.back();

// Notice: if you do parties_.emplace_back("DDD") here
// ptr may become invalid

ptr->value_ = "XXX";

for (auto& p : parties_) 
    std::cout << p.value_ << std::endl; // print: AAA\nBBB\nXXX


如果您想与重新分配的影响隔离开来,但可以接受 std::vector 作为所有者,从而决定您的对象的生命周期,您可以拥有一个 std::vector&lt;std::unique_ptr&lt;Party&gt;&gt;(同样,保持简单指向它们的指针/引用)

std::unordered_map<std::string, Party*> map_;
std::vector<std::unique_ptr<Party>> parties_;

parties_.emplace_back(std::make_unique<Party>("AAA"));
parties_.emplace_back(std::make_unique<Party>("BBB"));
parties_.emplace_back(std::make_unique<Party>("CCC"));

Party *ptr = parties_.back().get();

// Notice: if you do parties_.emplace_back(std::make_unique<Party>("DDD"));
// ptr will remain valid

ptr->value_ = "XXX";

for (auto& p : parties_) 
    std::cout << p.value_ << std::endl; // print: AAA\nBBB\nXXX

这确保对象是独立于向量分配的,但如果它们从向量中删除,它们将被删除。


OTOH,如果您想在矢量和地图之间共享所有权,您可能需要std::shared_ptr(但它不是免费提供的,它必须管理引用计数和 co。):

std::unordered_map<std::string, std::shared_ptr<Party>> map_;
std::vector<std::shared_ptr<Party>> parties_;

parties_.emplace_back(std::make_shared<Party>("AAA"));
parties_.emplace_back(std::make_shared<Party>("BBB"));
parties_.emplace_back(std::make_shared<Party>("CCC"));

std::shared_ptr<Party> ptr = parties_.back();

// Notice: if you do parties_.emplace_back(std::make_unique<Party>("DDD"));
// ptr will remain valid, but it will still be valid even after
// parties_.pop_back() (ptr will keep the pointed object alive)

ptr->value_ = "XXX";

for (auto& p : parties_) 
    std::cout << p.value_ << std::endl; // print: AAA\nBBB\nXXX

这可确保对象的生命周期与向量的生命周期无关,因为原始 std::shared_ptr 的任何副本都将 (1) 指向同一个对象并 (2) 使其保持活动状态。

【讨论】:

所有答案都解释了根本原因并提供了解决方案,但我最终选择了这个,因为 if 提供了多种替代方案,这些替代方案可能对偶然发现此问题并遇到稍微不同问题的其他人有用。 @resnet 谢谢!是的,这正是写这篇文章时的想法,尤其是在最初的问题中,并不完全清楚需要什么所有权模型(这很好,并不总是很明显!),我试过了为此类问题提供最常见的替代方案。【参考方案2】:

std::make_unique&lt;Party&gt;(parties_.back()) 总是创建一个 new 对象。它是std::unique_ptr&lt;Party&gt;(new Party(parties_.back())) 的包装器。请注意,parties_.back() 本身并没有复制任何内容,它返回一个引用。

在您的代码中ptr 不需要拥有Party,其指向:parties_ 已经是所有者。只需使用引用或原始指针:

Party &lastParty = parties_.back();
lastParty.value_ = "XXX";

【讨论】:

【参考方案3】:

这两个对象

std::unordered_map<std::string, std::unique_ptr<Party>> map_;
std::vector<Party> parties_;

两者都拥有Party 实例,因为std::vector 始终拥有它的元素,而std::unique_ptr 旨在专门拥有指针对象。您需要决定哪个容器应该拥有各方并管理它们的生命周期。示例:std::vector&lt;Party&gt; 拥有实例,那么您可以使用普通指针。

std::unordered_map<std::string, Party*> map_;
std::vector<Party> parties_;

当它们不涉及所有权语义时,可以使用普通指针。一旦你做出这个决定,原来的问题w.r.t。 parties_.back()std::make_unique(由于std::unique_ptr 的目的而总是创建一个新实例)不再是问题。

【讨论】:

以上是关于为啥 make_unique to vector::back() 创建另一个副本?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在VS2012中编写make_unique()?

命名空间“std”中没有名为“make_unique”的成员

std::make_unique SFINAE 友好吗?

c_cpp 的std :: make_unique

通过 make_unique/make_shared 调用 initializer_list 构造函数

std::make_unique 和 std::unique_ptr 内部有 new 的区别