我想知道的是,在传递它们、内存管理和在实践中使用它们方面,按值返回
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));
}
按值返回应被视为默认值。 (*) 偏离默认做法,返回
std::unique_ptr<Cat>
,应该需要理由。
返回指针有以下三个主要原因:
多态性。这是返回
std::unique_ptr<Cat>
而不是 Cat
的最佳理由:您实际上可能正在创建派生于 Cat
的类型的对象。如果您需要这种多态性,则绝对需要返回某种类型的指针。这就是工厂函数通常返回指针的原因。Cat
无法廉价移动或根本无法移动。 “天生”不动的类型很少见。你通常应该尝试通过使其便宜地可移动来修复Cat
。但当然 Cat
可能是其他人拥有的类型,您无法向其添加移动构造函数(甚至可能是复制构造函数)。在这种情况下,除了使用unique_ptr
(并向业主投诉)之外,你无能为力。该函数有可能失败并且无法构造任何有效的
Cat
。在这种情况下,一种可能性是按值返回,但如果无法构造 Cat
则抛出异常;另一种是在 C++11/C++14 中,让函数返回 std::unique_ptr<Cat>
,并在无法构造 Cat
时返回空指针。然而,在 C++17 中,在这种情况下,您应该开始返回 std::optional<Cat>
而不是 std::unique_ptr<Cat>
,以避免不必要的堆分配。(*) 当被调用的函数需要自己的值副本时,这也适用于传递对象,例如,将从其参数之一初始化类成员的构造函数。按值接受对象并移动。
一般来说,按值返回。
此规则的例外情况:
Cat
需要存在于堆上,以便比其创建代码更持久...但在这种情况下,也许它不应该真正返回一个unique_ptr
,而应该是一个shared_ptr
。Cat
,而是访问可以被解释为猫的东西;在这种情况下,您可能不需要一个唯一的指针,而是一个常规的指针(或带有自定义删除器的唯一指针)。Cat
是其产品之一,您可能还可以制作一只狗和一匹马,它们都是动物,因此您将返回一个 指向基类的指针,例如一个unique_ptr<Animal>
。这绝对是您会使用唯一指针的情况。Cat
很重要。我不同意@Brian 关于他建议的两个例外的回答:
nullptr
来指示失败。未能返回有效值就是异常的原因,即使您想避免它们 - 我建议返回 std::expected
(对于 C++23)或 std::optional
(早期的 C++ 版本)。或者如果允许的话,只是在失败时抛出异常。在内存管理方面它们完全不同。
当然,现在这些琐碎示例之间的实际功能差异非常小,假设移动语义可以使按值返回便宜(在第二个示例中,它们负责移动指针)。当然,如果您立即让所有内容超出范围,两个对象都会同时被销毁。
但是动态分配的代码远没有那么简单,并且添加了一个“为什么?”因素。
如果不检查函数返回后如何使用结果,你就无法真正进一步合理化这种差异。然后,有关自动内存分配与动态内存分配的所有典型考虑因素都会重新发挥作用。
总而言之,确实没有通用的、包罗万象的方法来告诉您工厂是否应该动态分配或按值返回。然而,就我个人而言,为了简单起见,我更喜欢后者(除非您知道不能),特别是如果您的对象类型通常是可移动的(由于 RVO,这可能不会对函数本身产生太大影响,但可能会帮助您在呼叫站点)。