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