Solar wrote:Being in a hurry as all too often recently, I read only the first page.
Such habits only get you into trouble.
Strange. I keep hearing about the "memory management issues of C and C++", but from my professional experience (which includes both Java and C++ assignments), they are no worse than keeping your event handlers properly unregistered in a Java/Swing application (a sure-fire way to have a memory leak in Java).
I'm not just talking about proper cleanup of resources, although that is an important consideration. Here are some of my thoughts on the Java/C++ comparison you bring up...
In Java, the case you mentioned is about the only case where something approximating a memory "leak" can occur (it is really "unintentional object retention", which is subtly different). Other than such cases of caching references, memory leaks are impossible. Also, there is a pre-canned solution to this problem: weak references. In C++, I can use smart pointers to solve a lot of the problems that GC solves in Java, but:
- If I am porting my code to old and crappy platforms with old and crappy compilers, I won't be able to use Boost so I will probably have to write my own.
- I've had to implement a lot of really nasty APIs that are defined in such a way that smart pointers don't help for the "large" objects, because their lifetimes are dictated by that API (they are not "shared until no one references them"). Using heap allocation for "small" objects is detrimental to performance (I expand on this later below).
- In Java, the cost of using "new" is very, very low compared to using "new" in most C/C++ run-time environments. Smart pointers don't help to mitigate this.
- GC is built into Java and programmers largely don't have to think about it. Even the lowliest C++ programmer who comes into contact with smart pointers needs to understand something of the mechanics of how they work.
Despite all this, I will grant you one thing: deterministic destruction in C++ is its greatest strength because it enables RAII. Java sorely lacks this (try/finally does not cut it). The "using" block in C# is sort of a half-step in the right direction.
However I see no reason for being forced to use a mechanism like RAII for memory as well as non-memory resources. RAII is a great way to unlock mutexes, close files, close database connections, etc., but I hate having to use it all the time for memory.
Memory management issues go beyond this however. I'll give you one example I run into every time I try to design an object in a graph that points to "child" objects -- there is no way, using STL, to provide an iterator over those child objects that doesn't reveal the type of container being used. I realize that because of typedefs, this is not really a big deal, but it's an easy example to understand (I have more, but the context is deeply rooted in what I'm doing at work, which would be difficult and/or illegal for me to explain). The reason this doesn't work is because of the "slicing problem" -- even if there were an iterator base class, you couldn't return it by value because of the slicing problem. You can't return it by reference, because then the iteration state would be part of the parent object and you would lose the ability for multiple threads to iterate over the collection of child objects simultaneously.
So, why not allocate the iterator on the heap and return a pointer to it? Even if you use an auto_ptr or some other smart pointer, it's crazy to do this because heap allocation is
slow (and, because of the lack of GC, deallocation is
synchronous as well as slow). If we were talking about "big" objects that are created rarely and live a long time, then this wouldn't be a big deal, but something like an iterator is small and is expected to be used frequently. That is why iterators are returned by value in C++, but anything returned by value in C++
forces you to abandon polymorphism. All because we're using the same crappy heap allocation routines from 30 years ago that are optimized for allocating large arrays in C. Yes, I could write my own allocator, in theory. No, I don't have the time or budget to do so. This is why C++ is a PITA for me.
We'd have to outlaw floating point maths, too, following your logic, because people keep forgetting it's not precise. While we're at it, outlaw integer maths too because divisions by zero may happen...
And how often to problems such as those occur, compared to some issue with performance, exception safety, scalability, or security caused by a memory management goof up?
Doing a quick find / grep / wc on my office workstation, I get 257 appearances of "new" in 79968 lines of (multithreading) C++ code, most of them in the server-/client module and where we're wrapping an underlying C API. All the rest is done on the stack, i.e. no "management" issues at all.
You may be fortunate enough to develop in an environment where you have complete control over the threading and memory model. I spend most of my time writing libraries (database drivers) that must implement a crappy 15-year-old C interface that has a well-defined memory management model, but one that conflicts terribly with its threading model, which was bolted on as an afterthought. In my current project for example, shared pointers have only limited usage. Most of the shared objects in the library have their lifetimes managed by the application using the library,
but because of the crazy hacked-on multithreading, we cannot assume anything about which application threads make calls to free those objects and when. Many of these objects have bi-directional links to each other, so it is difficult to break the links atomically. I have basically had to implement a domain-specific kind of transactional memory to make this work (my implementation is based on the idea of record locking in databases).
If you're thinking that all these problems are caused by a bad interface, you're right. However, I don't get to pick and choose which industry-standard APIs I have to implement. The reason I pound on C/C++ so much is more to do with bad interface design that pervades architectures rather than nitty-gritty coding issues. Like I said, if you're lucky enough to write applications or servers in C++ and you can control what platforms you have to support, your life will be
way easier than mine.
I really do not mean to offend you, but most if not all "memory management issues" I came across in C++ are the result of bullsh*t (or missing) design of basic utility classes, most often perpetrated by former Java programmers who don't know how a computer works.
You see, the sad fact of life that I've come to realize is that there are
a lot of those bit-ignorant former Java programmers out there. It sucks, but it's the truth. I've tried to explain a lot of the conventional C++ wisdom to such people -- the kind of thing that you and I take for granted every day. It's hard, because there is just so much background required to understand
why things in the language are done the way they are. Unfortunately, we can't rely on the expertise of developers because there just aren't enough "expert" developers.
In summary, why should I have to re-invent the wheel all the time just to provide a certain level of abstraction so that people can think about the problem domain instead of memory management performance issues, when the language, libraries, and run-time environment can be doing that for me? (Yes, there are C++ libraries. No, they are not nearly as portable as you think they are.)