从C++0x类方法返回unique_ptr

4
如果我的 SomeType 类有一个方法,该方法返回地图中的元素(使用键),例如:
std::unique_ptr<OtherType> get_othertype(std::string name)
{
   return otMap.find(name);
}

那么这样做会确保调用者接收到的是指向映射中对象的指针而不是副本?这样做可以吗,还是会尝试调用复制构造函数(并因已被删除而失败),因为正在返回它?
假设我必须使用unique_ptr作为我的映射项。
更新:
在尝试实现代码之后,似乎unique_ptr和std:map /:pair在gcc 4.4.4中无法一起使用,pair不喜欢unique_ptr作为类型参数。(请参见Can't create map of MoveConstructibles)。
我将ptr更改为std :: shared_ptr,所有内容都正常工作。
我想我可以使用相同的代码来处理共享指针?

顺便问一下,OtherType 到底是什么?它是一个带有虚成员函数的基类吗?你需要子类型多态性吗? - fredoverflow
这将是一个基类,具有像接口一样的纯虚方法。我只需要调用其中一个纯虚方法,无需向下转换。这会改变事情吗? - Mark
接口是使用unique_ptr的一个典型示例。没有更多问题。 - fredoverflow
谢谢,弗雷德。花时间了解和使用最好的工具来完成工作比基于无效假设盲目构建沙堆房子更好。 - Mark
4个回答

14

unique_ptr 的模型是所有权转移。如果从函数返回一个对象的 unique_ptr,则系统中没有其他 unique_ptr 可能引用相同的对象。

这是你想要的吗?我非常怀疑。当然,你可以简单地返回原始指针:

OtherType* get_othertype(const std::string& name)
{
    return otMap.find(name)->second.get();
}
因此,客户端可以访问对象,但映射仍然拥有它。
上述解决方案在找不到名称下的条目时相当脆弱。更好的解决方案是在这种情况下抛出异常或返回空指针:
#include <stdexcept>

OtherType* get_othertype(const std::string& name)
{
    auto it = otMap.find(name);
    if (it == otMap.end()) throw std::invalid_argument("entry not found");
    return it->second.get();
}

OtherType* get_othertype(const std::string& name)
{
    auto it = otMap.find(name);
    return (it == otMap.end()) ? 0 : it->second.get();
}

为了完整起见,这里是安东尼建议返回引用的代码:

OtherType& get_othertype(const std::string& name)
{
    auto it = otMap.find(name);
    if (it == otMap.end()) throw std::invalid_argument("entry not found");
    return *(it->second);
}

这里是如何返回map内unique_ptr的引用,但让我们将其改为常量引用,这样客户端就不会意外修改原始值:

unique_ptr<OtherType> const& get_othertype(const std::string& name)
{
    auto it = otMap.find(name);
    if (it == otMap.end()) throw std::invalid_argument("entry not found");
    return it->second;
}

1
那么,做这个的标准方法是什么?我希望被调用者拥有对象的指针,但映射仍然是所有者。但是我不想使用原始指针,因为被调用者可能不会处理它。如果我从映射中删除它,他的原始指针将指向空值,那会发生什么? - Mark
1
@Mark:这个对象在地图中是有保证的吗?如果是,那么你可以返回一个引用而不是指针,这样调用的语义更加明确:我会给你访问权限但不会转移所有权。 - David Rodríguez - dribeas
1
@Mark:是的,在函数末尾“弹出”本地原始指针根本没有任何效果。仅为了清晰起见,如果“弹出”本地unique_ptr,则它所引用的对象将立即被销毁。如果“弹出”shared_ptr,则相关引用计数将减少一个,如果已经达到零,则对象将被销毁。只是为了让您放心,根据您描述的用例,对我来说返回原始指针似乎是最好的选择。 - fredoverflow
4
我建议你返回一个引用而不是原始指针,这样可以清楚地表明 map 拥有该对象。当然,在这种情况下,如果对象不存在最好抛出异常。 - Anthony Williams
1
@roysc 在第23行,auto r = ... 会进行一次拷贝,即使 ... 是通过引用返回的。请尝试使用 auto& r - fredoverflow
显示剩余20条评论

2
otMap的类型是什么?
如果otMap.find(name)以rvalue形式返回std::unique_ptr<OtherType>,那么这将正常工作。然而,指向该值的所有权现在已经转移到了返回的指针,因此该值将不再存在于映射中。这意味着您使用的是自定义映射类型,而不是std::map<>
如果您想要在映射中保留该值并返回指向它的指针,则需要同时将std::shared_ptr<OtherType>用作映射值类型和get_othertype()的返回类型。
std::map<std::string,std::shared_ptr<OtherType>> otMap;
std::shared_ptr<OtherType> get_othertype(std::string name)
{
    auto found=otMap.find(name);
    if(found!=otMap.end())
        return found->second;
    return std::shared_ptr<OtherType>();
}

我正在遵循这里的另一个答案的建议:https://dev59.com/E1DTa4cB1Zd3GeqPKp0j,AshleysBrain建议使用unique_ptr存储在集合中。 - Mark
shared_ptr 相对于 unique_ptr 的开销很小(一个引用计数)。在这种情况下使用 unique_ptr 需要仔细考虑,除非它确实完全符合您的要求,否则我强烈建议不要使用。 - Anthony Williams
1
@Ant:我不同意。unique_ptr 明确表达了程序员希望 map 拥有 这些对象的意图,而且它可以使代码潜在地更快。正确实现 shared_ptr 必须考虑线程安全以正确计算引用计数,这可能比 unique_ptr 慢得多。 - fredoverflow
1
当将unique_ptr放入map中时,意味着map拥有该对象。但是,这会让您面临如何访问它的问题。如果直接访问它,例如使用otMap[name]->something,那么没问题,但返回原始指针则不太理想。shared_ptr的引用计数开销很小,除非对象实际上在线程之间共享。除非分析告诉我其他情况,否则足够小,我不会担心。 - Anthony Williams
@Anthony:客户端仍然可以从shared_ptr获取原始指针。C++要求负责任的使用,没有100%的安全性。 - fredoverflow
1
如果他们这样做,那是他们的问题。如果你给他们一个裸指针,那就是你的问题。我不喜欢那些给我裸指针的API,因为不清楚谁拥有它,或者它的生命周期是什么。 - Anthony Williams

0

otMap.find会返回一个rvalue,因此如果没有RVO(返回值优化),这个rvalue将被移动。但是,当然,现在你的map中没有那个特定的对象了。另外,我上次检查时发现,find返回的是迭代器,而不是值类型。


是的,我的错误,但我希望你能理解我的意思。 - Mark

0
你会考虑将 map 中的 unique_ptr 改为 shared_ptr 吗?这样做可以使返回值更加安全。unique_ptr 的整个意义在于它是唯一的(即不共享)。

2
重点在于独特的“所有权”。这并不排除其他原始指针指向同一对象的可能性。从这个意义上讲,与返回迭代器的容器相比,并没有太大的区别。 - sellibitze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接