Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

As I can see, smart pointers are used extensively in many real-world C++ projects.

Though some kind of smart pointers are obviously beneficial to support RAII and ownership transfers, there is also a trend of using shared pointers by default, as a way of "garbage collection", so that the programmer do not have to think about allocation that much.

Why are shared pointers more popular than integrating a proper garbage collector like Boehm GC? (Or do you agree at all, that they are more popular than actual GCs?)

I know about two advantages of conventional GCs over reference-counting:

  • Conventional GC algorithms has no problem with reference-cycles.
  • Reference-count is generally slower than a proper GC.

What are the reasons for using reference-counting smart pointers?

share|improve this question

migrated from stackoverflow.com Aug 14 '13 at 2:29

This question came from our site for professional and enthusiast programmers.

5  
I'd just add a comment that this is a wrong default to use: in most cases, std::unique_ptr is sufficient and as such has zero-overhead over raw pointers in terms of run-time performance. By using std::shared_ptr everywhere you'd also obscure the ownership semantics, losing one of the major benefits of smart pointers other than automatic resource management -- clear understanding of the intent behind the code. – Matt Aug 14 '13 at 17:32
1  
Sorry but the accepted answer here is completely wrong. Reference counting has higher overheads (a count instead of a mark bit and slower run-time performance), unbounded pause times when decrements avalanche and no more complex that, say, Cheney semi-space. – Jon Harrop Jan 26 at 20:45
up vote 49 down vote accepted

Some advantages of reference counting over garbage collection:

  1. Low overhead. Garbage collectors can be quite intrusive (e.g. making your program freeze up at unpredictable times while a garbage collection cycle processes) and quite memory-intensive (e.g. your process's memory footprint unnecessarily grows to many megabytes before garbage-collection finally kicks in)

  2. More predictable behavior. With reference counting, you are guaranteed that your object will be freed the instant the last reference to it goes away. With garbage collection, on the other hand, your object will be freed "sometime", when the system gets around to it. For RAM this isn't usually a big problem on desktops or lightly loaded servers, but for other resources (e.g. file handles) you often need to be closed ASAP even there.

  3. Simpler. Reference counting can be explained in a few minutes, and implemented in an hour or two. Garbage collectors, especially ones with decent performance, are extremely complex and not many people understand them.

  4. Standard. C++ includes reference counting (via shared_ptr) and friends in the STL, which means that most C++ programmers are familiar with it and most C++ code will work with it. There isn't any standard C++ garbage collector, though, which means that you have to choose one and hope it works -- and if it doesn't, it's your problem to fix, not the language's.

As for the alleged downsides of reference counting -- not detecting cycles is an issue, but one that I've never personally ran into in the last ten years of using reference counting. Most data structures are naturally acyclical, and if you do come across a situation where you need cyclical references (e.g. parent pointer in a tree node) you can just use a weak_ptr or a raw C pointer for the "backwards direction". As long as you are aware of the potential problem when you're designing your data structures, it's a non-issue.

As for performance, I've never had a problem with the performance of reference counting. I have had problems with the performance of garbage collection, in particular the random freeze-ups that GC can incur, to which the only solution ("don't allocate objects") might as well be rephrased as "don't use GC".

share|improve this answer
12  
Naïve reference-counting implementations typically get much lower throughput than production GCs (30–40%) at the expense of latency. The gap can be closed with optimisations such as using fewer bits for the refcount, and avoiding tracking objects until they escape—C++ does this last naturally if you mainly make_shared when returning. Still, latency tends to be the bigger problem in realtime applications, but throughput is more important generally, which is why tracing GCs are so widely used. I wouldn’t be so quick to speak badly of them. – Jon Purdy Aug 14 '13 at 6:13
3  
I'd quibble 'simpler': simpler in terms of the total amount of implementation required yes, but not simpler for the code that uses it: compare telling someone how to use RC ('do this when creating objects and this when destroying them') to how to (naively, which is often enough) use GC ('...'). – AakashM Aug 14 '13 at 8:08
2  
(although I'm probably hideously unaware of how easy smart pointers are to use in modern versions of C-ish languages) – AakashM Aug 14 '13 at 9:00
1  
C++ is incompatible with garbage collector. The C++11 specification undefined some behaviour to make boehm gc compliant, but IIRC there was some argument that it's still missing some bits. Does not explain why some other interpreters like perl or python hang on to the reference counting collectors (both have heap walk to deal with cycles too). – Jan Hudec Aug 14 '13 at 12:52
3  
"With reference counting, you are guaranteed that your object will be freed the instant the last reference to it goes away". That is a common misconception. flyingfrogblog.blogspot.co.uk/2013/10/… – Jon Harrop Feb 20 '15 at 14:34

To get good performance out of a GC, the GC needs to be able to move objects in memory. In a language like C++ where you can interact directly with memory locations, this is pretty much impossible. (Microsoft C++/CLR doesn't count because it introduces new syntax for GC-managed pointers and is thus effectively a different language.)

The Boehm GC, while a nifty idea, is actually the worst of both worlds: you need a malloc() that is slower than a good GC, and so you lose the deterministic allocation/deallocation behavior without the corresponding performance boost of a generational GC. Plus it is by necessity conservative, so it won't necessarily collect all your garbage anyway.

A good, well-tuned GC can be a great thing. But in a language like C++, the gains are minimal and the costs are often just not worth it.

It will be interesting to see, however, as C++11 becomes more popular, whether lambdas and capture semantics start to lead the C++ community towards the same kinds of allocation and object lifetime problems that caused the Lisp community to invent GCs in the first place.

See also my answer to a related question over on StackOverflow.

share|improve this answer
4  
RE the Boehm GC, I've occasionally wondered how much it personally is responsible for the traditional aversion to GC among C and C++ programmers simply by providing a bad first impression of the technology in general. – Leushenko Feb 19 '15 at 2:06
    
@Leushenko Well said. A case in point is this question, where Boehm gc is called a "proper" gc, ignoring the fact that it's slow and practically guaranteed to leak. I found this question while researching whether someone implemented python-style cycle breaker for shared_ptr, which sounds like a worthwhile goal for a c++ implementation. – user4815162342 Dec 17 '15 at 18:12

In MacOS X and iOS, and with developers using Objective-C or Swift, reference counting is popular because it is handled automatically, and the use of garbage collecting has considerably decreased since Apple doesn't support it anymore (I am told that apps using garbage collection will break in the next MacOS X version, and garbage collection was never implemented in iOS). I actually seriously doubt that there was ever much software using garbage collection when it was available.

The reason for getting rid of garbage collection: It never worked reliably in a C-style environment where pointers could "escape" to areas not accessible by the garbage collector. Apple strongly believed and believes that reference counting is faster. (You can make any claims here about relative speed, but nobody has been able to convince Apple). And in the end, nobody used garbage collection.

First thing that any MacOS X or iOS developer learns is how to handle reference cycles, so that's not a problem for a real developer.

share|improve this answer
    
The way I understand it, it wasn't that it's a C-like environment which decided things but that GC is indeterministic and needs much more memory in order to have acceptable performance, and outside server/desktop that's always a bit scarce. – Deduplicator Jan 27 at 9:19
    
Debugging why the garbage collector destroyed an object that I was still using (leading to a crash) decided it for me :-) – gnasher729 Jan 27 at 11:27
    
Oh yes, that would do it too. Did you in the end find out why? – Deduplicator Jan 27 at 11:29
    
Yes, it was one of many Unix functions where you pass a void* as a "context" which is then given back to you in a callback function; the void* was really an Objective-C object, and the garbage collector didn't realise the object was stashed away in the Unix call. Callback is called, casts void* to Object*, kaboom! – gnasher729 Jan 27 at 11:35

The biggest disadvantage of garbage collection in C++ is, that it's plain impossible to get right:

  • In C++, pointers do not live in their own walled community, they are mixed with other data. As such, you can't distinguish a pointer from other data that just happens to have a bit pattern that can be interpreted as a valid pointer.

    Consequence: Any C++ garbage collector will leak objects that should be collected.

  • In C++, you can do pointer arithmetic to derive pointers. As such, if you don't find a pointer to the start of a block, that does not mean that that block can't be referenced.

    Consequence: Any C++ garbage collector has to take these adjustments into account, treating any bit sequence that happens to point anywhere within a block, including right after the end of it, as a valid pointer that references the block.

    Note: No C++ garbage collector can handle code with tricks like these:

    int* array = new int[7];
    array--;    //undefined behavior, but people may be tempted anyway...
    for(int i = 1; i <= 7; i++) array[i] = i;
    

    True, this invokes undefined behavior. But some existing code is more clever than is good for it, and it may trigger preliminary deallocation by a garbage collector.

share|improve this answer
1  
"they are mixed with other data." It's not so much that they're "mixed" with other data. It's easy to use the C++ type system to see what's a pointer and what is not. The problem is that pointers frequently become other data. Hiding a pointer in an integer is an unfortunately common tool for many C-style APIs. – Nicol Bolas Jan 27 at 2:57
1  
You don't even need undefined behaviour to screw up a garbage collector in c++. You could, for example, serialize a pointer to a file and read it in later. In the meanwhile, your process may not contain that pointer anywhere in its address space, so the garbage collector could collect that object, and then when you deserialize the pointer... Whoops. – Bwmat Jan 27 at 4:36
    
@Bwmat "Even"? Writing pointers to a file like that seems a bit... far-fetched. Anyway, same serious problem plagues pointers to stack objects, they may be gone when you read the pointer back from file elsewhere in the code! Deserializing invalid pointer value is undefined behavior, don't do that. – hyde Jan 27 at 6:03
    
If course, you would need to be careful if you are doing something like that. It was meant to be an example that, in general, a garbage collector can't work 'properly' in all cases in c++ (without changing the language) – Bwmat Jan 27 at 6:06
1  
@gnasher729: Ehm, no? Past-end-pointers are perfectly fine? – Deduplicator Jan 27 at 12:40

As I can see, smart pointers are used extensively in many real-world C++ projects.

True but, objectively, the vast majority of code is now written in modern languages with tracing garbage collectors.

Though some kind of smart pointers are obviously beneficial to support RAII and ownership transfers, there is also a trend of using shared pointers by default, as a way of "garbage collection", so that the programmer do not have to think about allocation that much.

That's a bad idea because you still need to worry about cycles.

Why are shared pointers more popular than integrating a proper garbage collector like Boehm GC? (Or do you agree at all, that they are more popular than actual GCs?)

Oh wow, there are so many things wrong with your line of thinking:

  1. Boehm's GC is not a "proper" GC in any sense of the word. It is truly awful. It is conservative so it leaks and is inefficient by design. See: http://flyingfrogblog.blogspot.co.uk/search/label/boehm

  2. Shared pointers are, objectively, nowhere near as popular as GC because the vast majority of developers are using GC'd languages now and have no need of shared pointers. Just look at Java and Javascript in the job market compared to C++.

  3. You appear to be restricting consideration to C++ because, I assume, you think that GC is a tangential issue. It isn't (the only way to get a decent GC is to design the language and VM for it from the beginning) so you are introducing selection bias. People who really want proper garbage collection don't stick with C++.

What are the reasons for using reference-counting smart pointers?

You are restricted to C++ but wish you had automatic memory management.

share|improve this answer
1  
Um, it's a question tagged c++ which talks about C++ features. Clearly, any general statements are talking about within C++ code, not the entirety of programming. So however "objectively" garbage collection may be in use outside of the C++ world, that is ultimately irrelevant to the question at hand. – Nicol Bolas Jan 27 at 2:57
    
Your last line is patently wrong: You are in C++ and glad you aren't forced to deal with GC and it's delayed freeing of resources. There's a reason Apple doesn't like GC, and the most important guideline for GC'd languages is: Don't create any garbage unless you have gobs of idle resources or cannot help it. – Deduplicator Jan 27 at 9:09
    
@Deduplicator: "GC and it's delayed freeing of resources". All techniques produce floating garbage. Avoiding it requires you to solve the halting problem. – Jon Harrop Jan 27 at 12:36
    
@Deduplicator: "There's a reason Apple doesn't like GC". Yes, they are incompetent. There's a reason why Android uses GC and has more market share. – Jon Harrop Jan 27 at 12:36
1  
@Deduplicator: I can envisage programs that give either outcome. Reference counting would outperform tracing GC when the program is designed to keep heap allocate memory until it survives the nursery (e.g. a queue of lists) because that is pathological performance for a generational GC and would generate the most floating garbage. Tracing garbage collection would require less memory than scope-based reference counting when there are many small objects and lifetimes are short but not statically well known so something like a logic program using purely functional data structures. – Jon Harrop Jan 27 at 13:16

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.