我想知道的是,按值传递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));
}
答案 0 :(得分:10)
按值返回应视为默认值。 (*)退出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>
,以避免不必要的堆分配。
(*)这也适用于传递对象,当被调用的函数需要它自己的值副本时,例如一个构造函数,它将初始化一个类成员其中一个论点。按值接受对象并移动。
答案 1 :(得分:3)
默认情况下,按值返回。
此规则的例外情况:
unique_ptr
,而是{{1} }}。我不同意@ Brian关于他建议的两个例外的回答:
shared_ptr
来指示失败。未能返回有效值的是例外情况,即使您想要避免它们 - 我建议您返回nullptr
。答案 2 :(得分:0)
记忆管理方面,他们完全不同。
当然,这些日子里这些微不足道的例子之间的实际功能差异非常小,假设移动语义可以使得按值返回便宜(在第二个例子中,他们负责移动指针)。而且,当你只是让一切都超出范围时,两个物体将同时被摧毁。
但是动态分配的代码简单得多,并添加了“为什么?”因素。
如果在函数返回后检查结果将如何使用,则无法真正理解差异。然后关于自动与动态内存分配的所有典型考虑重新发挥作用。
总之,实际上没有通用的,全能的方式来告诉你工厂是应该动态分配还是按值返回。但是,我个人更喜欢后者的简单性(除非你知道你不能),特别是如果你的对象类型通常是可移动的(由于RVO可能对函数本身没有太大影响,但可能对你有所帮助)在呼叫现场)。