5

I'm converting a C# project to C++ and have a question about deleting objects after use. In C# the GC of course takes care of deleting objects, but in C++ it has to be done explicitly using the delete keyword.

My question is, is it ok to just follow each object's usage throughout a method and then delete it as soon as it goes out of scope (ie method end/re-assignment)?

I know though that the GC waits for a certain size of garbage (~1MB) before deleting; does it do this because there is an overhead when using delete?

As this is a game I am creating there will potentially be lots of objects being created and deleted every second, so would it be better to keep track of pointers that go out of scope, and once that size reachs 1MB to then delete the pointers?

(as a side note: later when the game is optimised, objects will be loaded once at startup so there is not much to delete during gameplay)

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
LynchDev
  • 793
  • 1
  • 12
  • 27

7 Answers7

9

Your problem is that you are using pointers in C++.

This is a fundamental problem that you must fix, then all your problems go away. As chance would have it, I got so fed up with this general trend that I created a set of presentation slides on this issue. – (CC BY, so feel free to use them).

Have a look at the slides. While they are certainly not entirely serious, the fundamental message is still true: Don’t use pointers. But more accurately, the message should read: Don’t use delete.

In your particular situation you might find yourself with a lot of long-lived small objects. This is indeed a situation which a modern GC handles quite well, and which reference-counting smart pointers (shared_ptr) handle less efficiently. If (and only if!) this becomes a performance problem, consider switching to a small object allocator library.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 1
    Well, I don't think you can do without pointers entirely, since you have to navigate between objects. And entity objects will have a concrete lifetime determined by program logic, and not scope, so you need dynamic allocation and delete for those. But people coming from Java or C# certainly do tend to overuse these features. – James Kanze Mar 07 '12 at 10:11
  • 3
    @James You’d be surprised how far you can come without pointers. I have used raw pointers exact once in recent years, in a library which for some good and some bad reasons didn’t allow smart pointers, and even there only at a single place (all the rest didn’t use pointers at all). – And I’ve written performance-critical and quite low level code. – Konrad Rudolph Mar 07 '12 at 10:14
  • I don't use pointers that much myself. But generally, raw pointers largely outnumber smart pointers (which are only used in special cases). And almost all pointers are at the application level (or very, very low level, e.g. an implementation of `std::vector`, or a user defined `operator new`). – James Kanze Mar 07 '12 at 11:18
  • @James I don’t know what you mean by “outnumber”. I’m actually serious: an application should contain *no* raw pointers except on the lowest library level. Assuming you’re not writing a library you shouldn’t have a single raw pointer in your code. Period. And even smart pointers should be the exception rather than the rule, unless you are dealing with large hierarchical object graphs. My presentation slides are *not* employing a hyperbole. – Konrad Rudolph Mar 07 '12 at 11:23
  • @James I agree that there are situations when using a GC or some kind of allocator coupled with raw pointers might be appropriate. But even then I would probably encapsulate the raw pointers to make the system pluggable. – Konrad Rudolph Mar 07 '12 at 11:26
  • Almost all application level pointers will be raw pointers. This for a moment as to _why_ you're using dynamic allocation, rather than a local or member variable. The object has a lifetime defined by the program logic, _not_ by implementation details. When the logic says it must be deleted, it must be deleted. The problem is the reverse of that solved by garbage collection or `shared_ptr`: the object's lifetime has ended, and the object (in its destructor) has to ensure that there are no more pointers to it (observer pattern, etc.). – James Kanze Mar 07 '12 at 11:50
  • @James All that can be modelled by smart pointers. In fact, VB6 (and some other systems) *only* had reference counted objects which are approximately `shared_ptr`s and apart from the problem with cyclic dependencies, this modelled lifetime perfectly. Likewise, all GCs use reachability analysis to determine the lifetime of an object. Program logic and object lifetime are *not* disconnected. And breaking dependency cycles should normally be easy since cycles don’t happen “just like that” but in well-defined circumstances. Again, this is no reason ever to use raw pointers on the application level – Konrad Rudolph Mar 07 '12 at 11:56
  • Python also uses reference counting. With special logic to handle cycles. But neither it nor the garbage collection in Java solve the basic problem that program logic requires the object to die, and there are still pointers to it. You need techniques to find those pointers, and remove them. Program logic and object lifetime are _not_ disconnected---`shared_ptr` disconnects them, and so isn't a solution. – James Kanze Mar 07 '12 at 12:00
  • @James I have never come across such a situation in any language. Can you give me an example? And `shared_ptr` does *not* disconnect object lifetime from program logic; on the contrary, it explicitly provides the program logic “shared ownership”. – Konrad Rudolph Mar 07 '12 at 12:09
  • `shared_ptr` makes object lifetime depend on the existence of pointers to the object, _not_ on the program logic. It's really no better than garbage collection in this respect, and neither solve the fundamental problem. (As far as I know, there is no good generic solution. When I first started using C++, over 20 years ago, there was a lot of talk about "relationship management", and products or classes which would handle this. As far as I know, however, no generic solution was found.) – James Kanze Mar 07 '12 at 12:21
  • @James Again, I have never come across a problem with this. In my experience, object lifetime is an excellent model for logical lifetime. After all, our business objects are *always* represented by objects in code. – Konrad Rudolph Mar 07 '12 at 12:23
  • Unfortunately I can only up vote this once. The slides are awesome and true. –  Mar 07 '12 at 12:43
  • @JamesKanze: Except most objects are resource objects which do not explicitly depend on the program logic. Those which do depend on the program logic can be easily managed by being either values or `unique-ptr`. – Puppy Mar 07 '12 at 12:48
  • 1
    @KonradRudolph I can't follow you, since about half of what you say supports my position. Business objects are represented by objects in code. Dynamically allocated objects, which have behavior, and react to external events. And at some point in time, an event says that their time has come, so we delete them. Regardless of any pointers to them which might exist: the problem of finding and removing such pointers is _not_ solved by things like `shared_ptr`. (But it depends on the application: if the object represents a record in a database, `shared_ptr` might make sense.) – James Kanze Mar 07 '12 at 13:55
  • 1
    @DeadMG They can't be managed by being values because they don't support copy and assignment---they are polymorphic and they have identity and behavior. They often are managed by `auto_ptr` during the creation phase, but they must outlive the transaction in which they were created, so at the latest, they are `release`d from the `auto_ptr` in the commit phase---often they are released earlier as they are passed to the transaction maanger. (Or the single operations are so simple that no transaction management is necessary.) – James Kanze Mar 07 '12 at 13:58
  • @James I still don’t think that weak pointers are that common. Where they are used, `weak_ptr` still has an advantage over a raw pointer since it was designed to be used in conjunction with ownership-maintaining smart pointers. – (And, responding to DeadMG’s comment) You haven’t explained why `unique_ptr`s cannot be used here. After all, they *do* support ownership transaction, and as such totally replace the (now obsolete) `auto_ptr`. – Konrad Rudolph Mar 07 '12 at 14:02
  • @KonradRudolph Who said anything about weak pointers. I've never used them; I've never seen any particular problem that they solve. (I don't use `shared_ptr` if the context is such that there might be graphs.) – James Kanze Mar 07 '12 at 14:36
  • @KonradRudolph Where would you put the `unique_ptr`? The whole point is that the lifetime of the object doesn't depend on any pointer. Shared, weak or otherwise. An object doesn't have an "owner" (which is an artificial concept introduced uniquely for the purposes of memory management). An object has a lifetime determined by the design of the program, not such low level implementation details. – James Kanze Mar 07 '12 at 14:39
  • @James Raw pointers = weak pointers. A `weak_ptr` behaves almost like a raw pointer except that it has explicit semantics (no ownership) and consequently you cannot delete it. And you can ensure that it’s still valid before accessing it. It’s essentially a stronger-typed raw pointer. – Konrad Rudolph Mar 07 '12 at 14:50
  • @James “an object doesn’t have an ‘owner’” – That’s the point though: So far, I have never found a situation where that wasn’t the case, or a convenient model. The “object lifetime” you speak of can be perfectly modelled by the lifetime of C++ objects (= either objects with automatic storage or owned pointers). – Konrad Rudolph Mar 07 '12 at 14:53
  • @JamesKanze: Useful, important, implementation details that determine how flexible your program is, how responsive/performant it is, etc. Abstraction is not a *useful goal* in as of itself. It has no value. If you have one program implementation, then it's not useful to abstract over that. As for `unique_ptr`, then it's usually held by whoever needs to determine when the object is destroyed. Which, under SRP, should never be the object itself. – Puppy Mar 07 '12 at 14:53
  • @KonradRudolph A `weak_ptr` can only be used if there is a `shared_ptr` somewhere to the object. Which is not often the case. And an object doesn't normally have an owner: value objects just "are"; the only thing you could consider owner is the stack frame, and that's not part of my model. And entity objects also just are: they are created by whoever handles the event which triggers their creation, used by whoever needs them (and can find them), and are destructed by whoever handles the event which triggers destruction (often the object itself, i.e. `delete this;`). – James Kanze Mar 07 '12 at 15:03
  • @James I’d still love an example of where this model breaks down. As I said, I can’t imagine many cases. There’s always a root object which naturally owns the resources and which itself resides on the stack. – Konrad Rudolph Mar 07 '12 at 15:05
  • @DeadMG Whoever needs to determine when the object is destroyed... isn't in the program. In a GUI, it's the user, clicking on a button. In a server, it's a client somewhere making a request. In a process management system, it's a piece of hardware. None of these can hold a `unique_ptr`, and until the event they generate arrives, you don't know who's going to get it. For the most part, these systems depend on some sort of map to go from an external identifier to the pointer: the map could be "owner", but removing an object from a map in order to delete it is a pretty obfuscated way around. – James Kanze Mar 07 '12 at 15:10
  • @KonradRudolph We're not talking about "resources", we're talking about entity objects which model external entities. – James Kanze Mar 07 '12 at 15:11
  • @James This is just confusing. An “object” in C++ is a memory location accessed by a pointer or an identifier. I’m suspecting that what you are calling “entity objects” are business objects? Either way, these *are* logically coupled to C++ objects. At least that’s what I’m asserting since I cannot think of a counter-example. Furthermore, I’m talking of “resources” to make it clear that this is an entity that needs clean-up (this is common terminology). – Konrad Rudolph Mar 07 '12 at 15:14
  • @KonradRudolph I'm definitely using "object" here in a more OO sense; my "objects" are implemented by C++ objects, but not every C++ object would correspond to one. And "entity objects" are the modeling objects in C++. In a business application, they could be business objects; in my network management applications, they were termination points and connections; in my GUI work, they were panes, buttons and such. Anything which has real "behavior", rather than just representing a value. – James Kanze Mar 07 '12 at 15:31
  • Tell me Konrad, do you consider the delete statement harmful? – Eric Mar 08 '12 at 08:09
  • @Eric As the answer says, yes. User code should never have `delete`. I’d argue that the same is true even for libraries. If you really need low-level control over allocation, you can (should?) destroy and deallocate separately, by using an explicit destructor call (`x.~T()`) and `allocator::deallocate`. I wouldn’t go as far as calling `delete` “evil” but it’s utterly useless. – Konrad Rudolph Mar 08 '12 at 11:14
  • Then my answer to you is that taking "considered harmful" positions on core language features is considered harmful. I accept that it's preferrable not to use delete yourself when alternatives like smart pointers (which btw are still pointers) are available, but I can't agree with taking it to the extreme of saying it should never be used and that if it must be used replace it with two, awkward, ugly explicit steps that hearken back to OOP in C. Everything in moderation. – Eric Mar 09 '12 at 06:49
  • @Eric The language evolved and *many* core language features of C++ are now in hindsight seen as doubtful. I doubt that, were C++ redesigned today, `delete` would be made part of the language. As for why I suggest using destruction and allocators instead, it’s simply because in such cases where you need explicit control, memory management and resource acquisition is decoupled (otherwise you could use a smart pointer). That is, you won’t call the two in consecutive order (then you could just use `delete` …) but in separate places. – Konrad Rudolph Mar 09 '12 at 09:43
5

You should be using RAII as much as possible in C++ so you do not have to explicitly deleteanything anytime.
Once you use RAII through smart pointers and your own resource managing classes every dynamic allocation you make will exist only till there are any possible references to it, You do not have to manage any resources explicitly.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • And how do you implement those fancy RAII objects without ever explicitly calling delete? Someone has to explicitly call delete sometime. RAII just makes it cleaner and more aligned with object scope. – Eric Mar 07 '12 at 09:57
  • 4
    @Eric Implementation detail. You’re also not generally concerned with the fact that the operating system is managing memory pages for you, are you? What goes on in the class stays in the class. – Konrad Rudolph Mar 07 '12 at 10:03
  • @Eric it is my understanding that RAII includes smart pointers. IT would be good to mention them explicitely though. – J.N. Mar 07 '12 at 10:10
  • This is a bad response: RAII is applicable for many things, but not for managing entity objects. In his case, he should drop all dynamic allocate completely for value types, and delete entity objects when the program logic requires it. RAII doesn't apply in either case. – James Kanze Mar 07 '12 at 10:13
  • My point is that no C++ of any scope gets by without an explicit call to delete somewhere sometime. Claiming otherwise is nonsense. – Eric Mar 07 '12 at 11:07
  • @Eric : well if you mean absolutely no detele, probably not, but in my work codebase of 60k loc, I think there are as few as a couple dozen deletes, and some are probably optional. In modern C++03 style, naked `new` and `deletes` are nearly non existent. By naked, I mean that there is a library (boost, stl, qt...) that is usually in charge with memory management. – J.N. Mar 07 '12 at 12:04
  • 1
    @Eric: then all well written C++ is nonsense. Of course, at some level, buried in a library somewhere, a few calls to delete are unavoidable. But if you ever do it in your application code, you are Doing It Wrong(tm) – jalf Mar 07 '12 at 12:45
3

Memory management in C# and C++ is completely different. You shouldn't try to mimic the behavior of .NET's GC in C++. In .NET allocating memory is super fast (basically moving a pointer) whereas freeing it is the heavy task. In C++ allocating memory isn't that lightweight for several reasons, mainly because a large enough chunk of memory has to be found. When memory chunks of different sizes are allocated and freed many times during the execution of the program the heap can get fragmented, containing many small "holes" of free memory. In .NET this won't happen because the GC will compact the heap. Freeing memory in C++ is quite fast, though.

Best practices in .NET don't necessarily work in C++. For example, pooling and reusing objects in .NET isn't recommended most of the time, because the objects get promoted to higher generations by the GC. The GC works best for short lived objects. On the other hand, pooling objects in C++ can be very useful to avoid heap fragmentation. Also, allocating a larger chunk of memory and using placement new can work great for many smaller objects that need to be allocated and freed frequently, as it can occur in games. Read up on general memory management techniques in C++ such as RAII or placement new.

Also, I'd recommend getting the books "Effective C++" and "More effective C++".

Andre Loker
  • 8,368
  • 1
  • 23
  • 36
3

Well, the simplest solution might be to just use garbage collection in C++. The Boehm collector works well, for example. Still, there are pros and cons (but porting code originally written in C# would be a likely candidate for a case where the pros largely outweigh the cons.)

Otherwise, if you convert the code to idiomatic C++, there shouldn't be that many dynamically allocated objects to worry about. Unlike C#, C++ has value semantics by default, and most of your short lived objects should be simply local variables, possibly copied if they are returned, but not allocated dynamically. In C++, dynamic allocation is normally only used for entity objects, whose lifetime depends on external events; e.g. a Monster is created at some random time, with a probability depending on the game state, and is deleted at some later time, in reaction to events which change the game state. In this case, you delete the object when the monster ceases to be part of the game. In C#, you probably have a dispose function, or something similar, for such objects, since they typically have concrete actions which must be carried out when they cease to exist—things like deregistering as an Observer, if that's one of the patterns you're using. In C++, this sort of thing is typically handled by the destructor, and instead of calling dispose, you call delete the object.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • @J.N. I obviously agree, or I wouldn't have mentioned it. But garbage collection isn't appropriate in all contexts either. I've used it effectively in some applications, but it probably wouldn't work with what I'm doing presently. – James Kanze Mar 07 '12 at 11:56
1

Substituting a shared_ptr in every instance that you use a reference in C# would get you the closest approximation at probably the lowest effort input when converting the code.

However you specifically mention following an objects use through a method and deleteing at the end - a better approach is not to new up the object at all but simply instantiate it inline/on the stack. In fact if you take this approach even for returned objects with the new copy semantics being introduced this becomes an efficient way to deal with returned objects also - so there is no need to use pointers in almost every scenario.

dice
  • 2,820
  • 1
  • 23
  • 34
  • Substituting a `shared_ptr` where ever you use a reference in C# will result in a lot of memory leaks (because of cyclic references), and will result in very poor and almost unmaintainable C++ (because dynamic allocation is being used where value semantics are more appropriate). – James Kanze Mar 07 '12 at 11:54
0

There are a lot more things to take into considerations when deallocating objects than just calling delete whenever it goes out of scope. You have to make sure that you only call delete once and only call it once all pointers to that object have gone out of scope. The garbage collector in .NET handles all of that for you.

The construct that is mostly corresponding to that in C++ is tr1::shared_ptr<> which keeps a reference counter to the object and deallocates when it drops to zero. A first approach to get things running would be to make all C# references in to C++ tr1::shared_ptr<>. Then you can go into those places where it is a performance bottleneck (only after you've verified with a profile that it is an actual bottleneck) and change to more efficient memory handling.

Anders Abel
  • 67,989
  • 17
  • 150
  • 217
  • For entity objects, you've got it backwards: you don't call delete once all pointers to it have disappeared, you arrange for the destructor to ensure that there are no more pointers to it (by notifying anyone who might hold a pointer). When you delete an object is determined by external program logic, not whether someone happens to hold a pointer or not. (Note that this is similar to C#, where you need some sort of `dispose` function to tell the world you're no longer there.) And you don't dynamically allocate other types, so the issue doesn't come up. – James Kanze Mar 07 '12 at 10:08
  • I might add that systematic use of `std::shared_ptr<>` is a guaranteed memory leak, because there will be cycles. – James Kanze Mar 07 '12 at 10:09
0

GC feature of c++ has been discussed a lot in SO.

Try Reading through this!!

Garbage Collection in C++

Community
  • 1
  • 1
Rohit Vipin Mathews
  • 11,629
  • 15
  • 57
  • 112