C++ 中的内存管理模式
我认为我对正常(功能)设计的模式有相当多的经验,例如描述的在四本书的帮派,我主要在java和C#中使用.在这些托管"语言中,这几乎是完成工作所需的一切.
I think I have a considerable experience with normal (functional) designed patters, as described e.g. in the gang of four book, which I mainly used in java and C#. In these "managed" languages this is pretty much everything you need to know to get your work done.
但是,在 C++ 世界中,开发人员还可以控制如何分配、传递和删除所有对象.我了解这些原则(我阅读了 Stroutrup 等)文本),但我仍然需要付出很多努力来决定哪种机制最适合给定的场景 - 这就是与内存相关的设计模式组合的用处.
However, in C++ world the developer also has the control of how all the objects get allocated, passed around and deleted. I understand the principles (I read Stroutrup among other texts), but it still takes me a lot of effort to decide which mechanism is best for a given scenario - and this is where a portfolio of memory-related design patterns would be useful.
例如,昨天我不得不创建一个 Results
类,它是几个对象的容器和另一个类型对象的集合(在本例中为 std::vector).所以有一些设计问题我无法真正回答:
For example, yesterday I had to create a class Results
, that was a container for a few objects and a collection (std::vector in this case) of yet another type of objects. So there are a few design questions I couldn't really answer:
- 我应该通过值还是智能指针返回这个类?
- 在类内部,向量和对象应该是普通成员,还是应该再次存储为智能指针?
- 在向量中,我应该直接存储对象,还是再次存储指向它们的智能指针?
- 在我的 Results 类中定义的 getter 应该返回什么(即值、引用或智能指针)?
当然,智能指针很酷,但也很酷,但它们会造成语法混乱,我不相信对每个对象都使用 malloc 是否是最佳方法.
Of course, smart pointers are cool and what not, but they create syntactic clutter and I am not convinced if using malloc for every single object is optimal approach.
对于上述具体问题的回答,我将不胜感激,但对于与内存相关的设计模式的一些更长、更笼统的文本,我将不胜感激 - 这样我也可以解决周一遇到的问题!
I would be grateful for answers for the specific points above, but even more for some longer and more general texts on memory-related design patterns - so that I can solve the problems I will have on Mondays as well!
推荐答案
你所有问题的答案最终都是一样的:这取决于你是需要引用语义还是值语义(需要考虑一些警告).
The answer to all of your questions ends up being one and the same: it depends on whether you need reference semantics or value semantics (with some caveats to be taken into account).
如果您需要引用语义,这是您在 Java 和 C# 等语言中针对使用 class
关键字声明的 UDT(用户定义的数据类型)的默认设置,那么您将不得不使用智能指针.在这种情况下,您希望多个客户端为特定对象保存安全别名,其中 safe 一词封装了这两个要求:
If you need reference semantics, which is what you have by default in languages like Java and C# for UDTs (User-defined Data Types) declared with the class
keyword, then you will have to go for smart pointers. In this scenario you want several clients to hold safe aliases to a specific object, where the word safe encapsulates these two requirements:
- 避免悬空引用,这样您就不会尝试访问不再存在的对象;
- 避免对象的生命周期超过对它们的所有引用,以免内存泄漏.
这就是智能指针所做的.如果您需要引用语义(并且如果您的算法不会在需要共享所有权的情况下使引用计数的开销变得重要),那么您应该使用智能指针.
This is what smart pointers do. If you need reference semantics (and if your algorithms are not such to make the overhead of reference counting significant where shared ownership is needed), then you should use smart pointers.
您确实需要引用语义,例如,当您希望同一个对象成为多个集合的一部分时.当您更新一个集合中的对象时,您希望所有其他集合中同一对象的表示一致地更新.在这种情况下,您可以在这些集合中存储指向对象的智能指针.智能指针封装了一个对象的身份,而不是它的值.
You do need reference semantics, for instance, when you want the same object to be part of several collections. When you update the object in one collection, you want the representations of the same object in all the other collections to be consistently updated. In this case, you store smart pointers to your objects in those collections. Smart pointers encapsulate the identity of an object rather than its value.
但如果您不需要创建别名,那么值语义可能是您应该依赖的.当您声明一个具有自动存储的对象(即在堆栈上)时,这就是您在 C++ 中默认得到的.
But if you do not need to create aliases, then value semantics is probably what you should rely on. This is what you get by default in C++ when you declare an object with automatic storage (i.e. on the stack).
需要考虑的一件事是,STL 集合存储值,所以如果您有一个vector
,那么copys>T
将存储在您的 vector
中.总是假设你不需要引用语义,如果你的对象很大而且复制成本很高,这可能会成为一种开销.
One thing to consider is that STL collections store values, so if you have a vector<T>
, then copies of T
will be stored in your vector
. Always supposing that you do not need reference semantics, this might become anyway an overhead if your objects are big and expensive to copy around.
为了限制这种情况的可能性,C++11 附带了move 操作,这使得在不再需要对象的旧副本时可以有效地按值传输对象.
To limit the likelyhood of this scenario, C++11 comes with move operations, which make it possible to efficiently transfer objects by value when the old copy of the object is no more needed.
我现在将尝试使用上述概念更直接地回答您的问题.
I will now try to use the above concepts to answer your questions more directly.
1) 我应该通过值返回这个类,还是通过智能指针?
这取决于您是否需要引用语义.函数对那个对象做了什么?该函数返回的对象是否应该由许多客户端共享?如果是,则通过智能指针.如果没有,是否可以定义有效的移动操作(几乎总是如此)?如果是,则按值.如果没有,通过智能指针.
It depends on whether you need reference semantics or not. What does the function do with that object? Is the object returned by that function supposed to be shared by many clients? If so, then by smart pointer. If not, is it possible to define an efficient move operation (this is almost always the case)? If so, then by value. If not, by smart pointer.
2) 在类内部,向量和对象应该是普通成员,还是应该再次存储为智能指针?
最有可能是普通成员,因为向量通常在概念上是对象的一部分,因此它们的生命周期与嵌入它们的对象的生命周期绑定.在这种情况下,您很少需要引用语义,但如果需要,请使用智能指针.
Most likely as normal members, since vectors are usually meant to be conceptually a part of your object, and their lifetime is therefore bound to the lifetime of the object that embeds them. You rarely want reference semantics in such a scenario, but if you do, then use smart pointers.
3) 在向量中,我应该直接存储对象,还是再次指向它们的智能指针?
与第 1) 点的答案相同:您需要共享这些对象吗?你应该为这些对象存储别名吗?您是否希望在引用这些对象的代码的不同部分中看到对这些对象的更改?如果是这样,则使用共享指针.如果没有,是否可以有效地复制和/或移动这些对象?如果是这样(大部分时间),存储值.如果没有,请存储智能指针.
Same answer as for point 1): do you need to share those objects? Are you supposed to store aliases to those objects? Do you want changes to those objects to be seen in different parts of your code which refer those objects? If so, then use shared pointers. If not, is it possible to efficiently copy and/or move those objects? If so (most of the time), store values. If not, store smart pointers.
4) 在我的 Results 类中定义的 getter 应该返回什么(即值、引用或智能指针)?
与第 2 点相同的答案:这取决于您计划对返回的对象做什么:您是否希望它们由您的代码的许多部分共享?如果是,则返回一个智能指针.如果它们仅由一个部分独家拥有,则按价值返回,除非移动/复制这些对象太昂贵或根本不允许(不太可能).在这种情况下,返回一个智能指针.
Same answer as for point 2): it depends on what you plan to do with the returned objects: do you want them to be shared by many parts of your code? If so, return a smart pointer. If they shall be exclusively owned by just one part, return by value, unless moving/copying those objects is too expensive or not allowed at all (quite unlikely). In that case, return a smart pointer.
作为旁注,请注意 C++ 中的智能指针比 Java/C# 引用要棘手一些:首先,根据是否共享所有权,您有两种主要的智能指针风格(shared_ptr
) 或 唯一所有权 (unique_ptr
) 是需要的.其次,您需要避免对 shared_ptr
的循环引用,这会创建对象孤岛,即使您的运行代码无法再访问它们,这些对象也会使彼此保持活动状态.这就是存在弱指针(weak_ptr
)的原因.
As a side note, please be aware that smart pointers in C++ are a bit trickier than Java/C# references: first of all, you have two main flavors of smart pointers depending on whether shared ownership (shared_ptr
) or unique ownership (unique_ptr
) is desired. Secondly, you need to avoid circular references of shared_ptr
, which would create islands of objects that keep each other alive even though they are no more reachable by your running code. This is the reason why weak pointers (weak_ptr
) exist.
这些概念自然会导致责任的概念,用于管理对象的生命周期或(更一般地)管理所用资源.例如,您可能想了解 RAII 习语(资源获取即初始化),以及一般的异常处理(编写异常安全代码是这些技术存在的主要原因之一).
These concept naturally lead to the concept of responsibility for managing the lifetime of an object or (more generally) the management of a used resource. You might want to read about the RAII idiom for instance (Resource Acquisition Is Initialization), and about exception handling in general (writing exception-safe code is one of the main reasons why these techniques exist).
相关文章