你所有问题的答案最终都是一样的:这取决于你是否需要引用语义或值语义(需要考虑一些警告)。
如果您需要引用语义,这是在 Java 和 C# 等语言中默认使用class
关键字声明的 UDT(用户定义的数据类型)所具有的,那么您将不得不使用智能指针。在这种情况下,您希望多个客户端为特定对象保存安全别名,其中安全一词封装了这两个要求:
- 避免悬空引用,这样您就不会尝试访问不再存在的对象;
- 避免使用比对它们的所有引用都寿命更长的对象,这样您就不会泄漏内存。
这就是智能指针的作用。如果您需要引用语义(并且如果您的算法不会在需要共享所有权的情况下使引用计数的开销显着增加),那么您应该使用智能指针。
例如,当您希望同一个对象成为多个集合的一部分时,您确实需要引用语义。当您更新一个集合中的对象时,您希望所有其他集合中的同一对象的表示得到一致更新。在这种情况下,您将智能指针存储到这些集合中的对象。智能指针封装对象的标识而不是其值。
但是如果你不需要创建别名,那么值语义可能是你应该依赖的。当您声明一个具有自动存储功能的对象(即在堆栈上)时,这就是您在 C++ 中默认获得的内容。
需要考虑的一件事是 STL 集合存储值,因此如果您有vector<T>
,那么的副本将T
存储在您的vector
. 总是假设您不需要引用语义,如果您的对象很大且复制成本很高,这可能会成为开销。
为了限制这种情况的可能性,C++11 提供了移动操作,当不再需要对象的旧副本时,它可以通过值有效地传输对象。
我现在将尝试使用上述概念更直接地回答您的问题。
1)我应该通过值还是通过智能指针返回这个类?
这取决于您是否需要引用语义。该函数对该对象有什么作用?该函数返回的对象是否应该由许多客户端共享?如果是这样,那么通过智能指针。如果没有,是否可以定义一个有效的移动操作(几乎总是如此)?如果是这样,那么按价值计算。如果没有,通过智能指针。
2)在类内部,向量和对象应该是普通成员,还是应该再次存储为智能指针?
最有可能作为普通成员,因为向量通常在概念上是对象的一部分,因此它们的生命周期与嵌入它们的对象的生命周期绑定。在这种情况下,您很少需要引用语义,但如果需要,请使用智能指针。
3)在向量中,我应该直接存储对象,还是再次存储指向它们的智能指针?
与第 1 点的答案相同):您需要共享这些对象吗?您是否应该为这些对象存储别名?您是否希望在引用这些对象的代码的不同部分中看到对这些对象的更改?如果是这样,则使用共享指针。如果没有,是否可以有效地复制和/或移动这些对象?如果是这样(大部分时间),请存储值。如果没有,存储智能指针。
4)我的Results 类上定义的getter 应该返回什么(即值、引用或智能指针)?
与第 2 点相同的答案):这取决于您打算如何处理返回的对象:您是否希望它们被代码的许多部分共享?如果是,则返回一个智能指针。如果它们应仅由一个部分独占,则按价值返回,除非移动/复制这些对象太昂贵或根本不允许(不太可能)。在这种情况下,返回一个智能指针。
作为旁注,请注意 C++ 中的智能指针比 Java/C# 引用要复杂一些:首先,您有两种主要的智能指针风格,具体取决于是否需要共享所有权( shared_ptr
) 或唯一所有权( unique_ptr
)。其次,您需要避免 的循环引用shared_ptr
,这会创建使彼此保持活动状态的对象岛,即使您正在运行的代码无法访问它们。这就是弱指针( weak_ptr
) 存在的原因。
这些概念自然地导致了管理对象生命周期或(更一般地)管理已用资源的责任概念。例如,您可能想了解 RAII 习语(Resource Acquisition Is Initialization),以及一般的异常处理(编写异常安全代码是这些技术存在的主要原因之一)。