我认为我对正常(功能)设计的图案有相当多的经验,如在the gang of four book中,我主要用于java和C#。在这些“托管”语言中,这几乎是您完成工作所需要知道的一切。
但是,在C ++世界中,开发人员还可以控制所有对象的分配,传递和删除。我理解这些原则(我在其他文本中阅读Stroutrup),但我仍然需要花费很多精力来决定哪种机制最适合给定的场景 - 这就是与内存相关的设计模式组合的地方很有用。
例如,昨天我不得不创建一个类Results
,它是一些容器和一个集合(在这种情况下是std :: vector)的另一种类型的对象。所以有一些我无法回答的设计问题:
当然,智能指针很酷而且没有,但它们会产生语法混乱,我不相信是否为每个对象使用malloc是最佳方法。
我将非常感谢上述具体问题的答案,但对于与记忆相关的设计模式的一些更长和更一般的文本更是如此 - 这样我就可以解决周一我将遇到的问题!
答案 0 :(得分:23)
你所有问题的答案最终都是同一个:这取决于你是否需要引用语义或价值语义(需要一些警告考虑到了。)
如果您需要引用语义,这是您在使用class
关键字声明的UDT(用户定义数据类型)的Java和C#等语言中的默认设置,那么您将不得不去寻找智能指针。在这种情况下,您希望多个客户端将安全别名保存到特定对象,其中 safe 一词封装了这两个要求:
这是智能指针的功能。 如果您需要引用语义(并且如果您的算法不能在需要共享所有权的情况下使引用计数的开销很大),那么则应该使用智能指针。< / p>
例如,当您希望同一对象成为多个集合的一部分时,您确实需要引用语义。在一个集合中更新对象时,您希望始终更新所有其他集合中同一对象的表示。在这种情况下,您将智能指针存储到这些集合中的对象。智能指针封装了对象的身份而不是其值。
但是如果你不需要创建别名,那么值语义可能是你应该依赖的。当您使用自动存储(即在堆栈上)声明对象时,这是默认情况下在C ++中获得的。
要考虑的一件事是STL集合存储值,因此如果您有vector<T>
,则会存储T
的副本在vector
中。总是假设您不需要引用语义,如果您的对象庞大且复制昂贵,这可能无论如何都会成为开销。
为了限制这种情况的可能性,C ++ 11附带了 move 操作,这样就可以在不再需要对象的旧副本时按值有效地传输对象。 / p>
我现在将尝试使用上述概念更直接地回答您的问题。
1)我应该按值还是通过智能指针返回此类?
这取决于您是否需要引用语义。该功能对该对象有何作用?该函数返回的对象应该被许多客户端共享吗?如果是这样,那么通过智能指针。如果没有,是否可以定义有效的移动操作(这几乎总是如此)?如果是这样,则按价值计算。如果没有,请通过智能指针。
2)在课堂内,矢量和对象应该是普通成员,还是应该再次存储为智能指针?
最有可能作为普通成员,因为向量通常意味着在概念上是对象的一部分,因此它们的生命周期与嵌入它们的对象的生命周期有关。在这种情况下,您很少需要引用语义,但如果这样做,则使用智能指针。
3)在向量中,我应该直接存储对象,还是再次指向智能指针?
与第1点相同的答案):你需要分享这些对象吗?你应该将别名存储到那些对象吗?您是否希望在代码的不同部分中看到对这些对象的更改?如果是,则使用共享指针。如果没有,是否可以有效地复制和/或移动这些对象?如果是这样(大多数时候),存储值。如果没有,请存储智能指针。
4)我的Results类中定义的getter应该返回什么(即值,引用或智能指针)?
与第2点相同的答案:它取决于您打算如何处理返回的对象:您希望它们被代码的许多部分共享吗?如果是这样,返回一个智能指针。如果它们仅由一个部分独占,则按值返回,除非移动/复制这些对象太昂贵或根本不允许(非常不可能)。在这种情况下,返回一个智能指针。
作为旁注,请注意C ++中的智能指针比Java / C#引用有点棘手:首先,根据是否共享所有权,您有两种主要的智能指针: (shared_ptr
)或唯一所有权(unique_ptr
)是理想的。其次,你需要避免使用shared_ptr
的循环引用,它会创建对象的孤岛,即使你的运行代码无法再访问它们,它们也能保持活着。这就是弱指针(weak_ptr
)存在的原因。
这些概念自然导致责任的概念,用于管理对象的生命周期或(更一般地)管理已使用的资源。您可能希望阅读有关RAII习惯用法(资源获取是初始化)以及一般的异常处理(编写异常安全代码是存在这些技术的主要原因之一)。