应该何时返回值,而不是返回唯一指针

时间:2017-06-08 23:58:25

标签: c++ c++11 memory-management unique-ptr return-by-value

我想知道的是,按值传递Cat与传递std::unique_ptr<Cat>实际上与传递它们,内存管理以及在实践中使用它们有什么不同。

内存管理明智,不一样吗?因为返回值对象和包装在unique_ptr中的对象,一旦它们超出范围,它们的析构函数会被触发吗?

那么,您如何比较两段代码:

Cat catFactory(string catName) {
    return Cat(catName);
}

std::unique_ptr<Cat> catFactory(string catName) {
    return std::unique_ptr(new Cat(catName));
}

3 个答案:

答案 0 :(得分:10)

按值返回应视为默认值。 (*)退出std::unique_ptr<Cat>,偏离默认惯例,应该要求理由。

返回指针有三个主要原因:

  1. 多态性。这是返回std::unique_ptr<Cat>而不是Cat的最佳理由:您实际上可能正在创建从Cat派生的类型的对象。如果你需要这种多态性,你绝对需要返回某种指针。这就是工厂函数通常返回指针的原因。

  2. Cat不能低价移动或根本无法移动。 “固有的”不可移动的类型是罕见的;你通常应该尝试通过降低Cat来实现它的便宜动作。但当然Cat可能是其他人拥有的类型,您无法添加移动构造函数(或者甚至是复制构造函数)。在这种情况下,除了使用unique_ptr(并向所有者投诉)之外,您无能为力。

  3. 该函数可能会失败,无法构造任何有效的Cat。在这种情况下,一种可能性是按值返回,但如果无法构造Cat则抛出异常;另一方面,在C ++ 11 / C ++ 14中,使函数返回std::unique_ptr<Cat>并在没有构造Cat时返回空指针。但是,在C ++ 17中,在这种情况下,您应该开始返回std::optional<Cat>而不是std::unique_ptr<Cat>,以避免不必要的堆分配。

  4. (*)这也适用于传递对象,当被调用的函数需要它自己的值副本时,例如一个构造函数,它将初始化一个类成员其中一个论点。按值接受对象并移动。

答案 1 :(得分:3)

默认情况下,按值返回。

此规则的例外情况:

  1. Cat需要存在于堆上,以便比触发其创建的代码更久......但在这种情况下,它可能不应该是返回的unique_ptr,而是{{1} }}。
  2. 你实际上并没有构建Cat,而是获得可以被解释为Cat的东西;在这种情况下,再次,你可能不想要一个唯一的指针,而是一个常规指针(或一个带有自定义删除器的唯一指针)。
  3. 多态性 - 如果它是一个工厂而猫是它的产品之一,你可以制作一只狗和一只马,都是动物,所以你会返回一个指向动物的指针。这绝对是你使用独特指针的情况。
  4. 你的副本,作业和/或移动构造者中的黑暗伏都教,这使得重要的是始终确保你只从远处戳你的猫。
  5. 我不同意@ Brian关于他建议的两个例外的回答:

    • 我建议使用指针返回类型,以便能够通过返回shared_ptr来指示失败。未能返回有效值的是例外情况,即使您想要避免它们 - 我建议您返回nullptr
    • 您通常不需要移动构造函数来启动返回值 - 因此缺少移动构造函数不应该是返回指针的理由。

答案 2 :(得分:0)

记忆管理方面,他们完全不同。

当然,这些日子里这些微不足道的例子之间的实际功能差异非常小,假设移动语义可以使得按值返回便宜(在第二个例子中,他们负责移动指针)。而且,当你只是让一切都超出范围时,两个物体将同时被摧毁。

但是动态分配的代码简单得多,并添加了“为什么?”因素。

如果在函数返回后检查结果将如何使用,则无法真正理解差异。然后关于自动与动态内存分配的所有典型考虑重新发挥作用。

总之,实际上没有通用的,全能的方式来告诉你工厂是应该动态分配还是按值返回。但是,我个人更喜欢后者的简单性(除非你知道你不能),特别是如果你的对象类型通常是可移动的(由于RVO可能对函数本身没有太大影响,但可能对你有所帮助)在呼叫现场)。