7

我想知道的是,在传递它们、内存管理和在实践中使用它们方面,按值返回 aCat实际上与返回a 有何不同。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));
}
4

3 回答 3

15

按值返回应被视为默认值。(*) 偏离默认做法,通过返回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>no 时让它返回一个空指针。Cat然而,在 C++17 中,您应该开始返回std::optional<Cat>而不是std::unique_ptr<Cat>在这种情况下,以避免不必要的堆分配。

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

于 2017-06-09T00:08:29.143 回答
4

默认情况下,按值返回。

此规则的例外情况:

  1. Cat 需要存在于堆上,以便比触发其创建的代码更持久......但在这种情况下,它可能不应该是真正unique_ptr返回的 a,而是一个shared_ptr.
  2. 您实际上并不是在构建 Cat,而是访问可以被解释为 Cat 的东西;在这种情况下,同样,您可能不想要一个唯一指针,而是一个常规指针(或带有自定义删除器的唯一指针)。
  3. 多态性 - 如果它是一家工厂,并且 Cat 是它的产品之一,那么您可能还可以制作 Dog 和 Horse,它们都是 Animals,因此您将返回指向 Animal 的指针。这绝对是您将使用唯一指针的情况。
  4. 你的副本、赋值和/或移动构造函数中的黑暗巫术,这使得始终确保你只从远处戳你的猫很重要。

我不同意@Brian 关于他建议的两个例外的回答:

  • 我建议不要使用指针返回类型,以便能够通过返回来指示失败nullptr。未能返回有效值是异常的原因,即使您想避免它们 - 我建议返回一个std::optional.
  • 您通常不需要移动构造函数来进行返回值优化 - 因此缺少移动构造函数不应成为返回指针的理由。
于 2017-06-09T00:32:12.583 回答
0

在内存管理方面,它们完全不同。

当然,这些天来,这些琐碎示例之间的实际功能差异非常小,假设移动语义可以使按值返回便宜(在第二个示例中,它们负责移动指针)。而且,当然,如果您立即让所有内容超出范围,这两个对象将同时被销毁。

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

如果不检查函数返回后将如何使用结果,您将无法真正进一步合理化差异。然后,所有关于自动与动态内存分配的典型考虑都会重新发挥作用。

总之,实际上没有通用的、包罗万象的方法来告诉您工厂应该动态分配还是按值返回。但是,为了简单起见,我个人更喜欢后者(除非您知道不能),特别是如果您的对象类型通常是可移动的(由于 RVO,这可能不会对函数本身产生太大影响,但可能会对您有所帮助在呼叫点)。

于 2017-06-09T00:08:00.030 回答