There seems to be a divide in ideas about C++ resource management, which I’m going to grossly exaggerate and call the “Stroustrup” and the “Sutter” positions, based on comments by these titans in recent online videos. These are of course not opposing positions, merely shades of opinion that depend on the application domain and target environment. But there does seem to be an underlying difference of emphasis on how resource management, and memory management in particular, are best dealt with in modern C++.
|Focus on stack object managing resource||Focus on heap object conveniently managed by smart pointer|
|Prefer handle/body idiom||Prefer smart-pointers (shared_ptr/unique_ptr)|
|Memory is just one of many resources (file handle, mutex, …)||Memory is special because it’s the most common resource|
|While C++ is broadly used, C++ is the best language for embedded or other small-memory architectures and C++ training should stress techniques that, while applicable anywhere, are most important in these environments||C++ is one competing language in many application domains and C++ teaching should stress how productive developers can be with modern C++|
|Handle/body completely hides heap allocation as implementation detail||Sharing heap objects is common, shared_ptr/unique_ptr make lifetime and sharing semantics clear|
|Sharing objects much more uncommon than people think, shared_ptr only useful in small number of cases||Smart-pointers are a useful default choice that add safety with no serious overhead in most environments|
|Detailed understanding of object and resource lifetime is an important part of application development. In small-memory architectures (including high-volume web-servers as well as small portable devices) this is key to keeping memory requirements down.||Applications can be written as quickly in modern C++ with smart pointers as in other languages while still avoiding most memory issues common to C and early C++. This may lead to higher memory usage than is optimal but with C++ this can be optimised later if needed.|
My own position is informed by both hearing and reading these opinions from people with much wider experience and more depth of knowledge than mine, but also strongly based on my own experience and every day practice, mostly in business application development.
The “Stroustroup” approach is more concerned with memory efficiency and detailed understanding of the application. The “Sutter” approach is more concerned with developer productivity which is more of a concern in environments where C++ has to compete with other high-level languages such as Java and C#. This makes the “Sutter” position very practical for most types of application development, even for some constrained environments such as smart-phones, but would be inappropriate for other environments such as pacemakers or critical real-time environments.
So my own position:
- Each resource should be owned by one specific object
- Safer under exceptions, easier to understand
- You have two resources managed by your class? Each should be managed by a separate member variable in your class, your class should directly manage neither.
- Handle classes preferred for other (non-memory) resources
- Clear classes for lock, stream, socket, thread where copy/sharing semantics are very specific and need to be clearly considered.
- shared_ptr/unique_ptr preferred for heap memory resources
- In my application domain memory is by far the most common resource: 1000s of heap objects vs 10s of window handles, a few threads, a few file handles, a few network sockets
- Factory functions for objects only exposing an interface/abstract-base-class are common and creating handle/body seems unnecessary when a smart-ptr gives same result
- shared_ptr with custom delete function should mostly be avoided
- Custom delete should only be used when it’s still just managing a heap object, not when the shared_ptr is actually meaning something quite different.
- Custom delete functions are less readable than a custom handle object for non-memory resources – e.g. shared-ptr<FILE> file( fp, fclose );
- Custom delete functions to run code-at-exit is clever but probably less readable than a custom object with your code in the destructor – e.g. shared_ptr<void> autoDoItLater( 0, doSomethingAtEndOfScope );
- But a custom delete function is sometimes useful for safely using separate heaps or specific release functions from other libraries. No semantic change, still just cleaning up heap memory, but modifying how the memory is cleaned up.
- shared_ptr/unique_ptr sharing/copying semantics are clearer than handle/body semantics
- A handle might be copyable but what does it mean? Copying a stream would mean another open stream on the same file, changes from either are visible to both. But copying a string gives a separate string, changes to one don’t change the other. This can be documented but is not obvious from the interface. This can be avoided by sharing the object with a pointer, maybe a shared_ptr – oh, back where we started.
- Copying a stack object that might or might not be a handle (hidden implementation detail, remember)? Copy a mutex and get another reference to the same OS mutex? Or a new OS mutex with similar characteristics? What if I didn’t know the mutex class was a handle to an OS object rather than a language feature, would that change how I’d guess?
- Copy a shared_ptr and it’s clearly a shallow copy, and references the same shared heap object. You could argue this is also just documentation but it’s in the name and it’s so common it quickly becomes internal knowledge.
- Bjarne Stroustrup http://www.infoq.com/presentations/Cplusplus-11-Bjarne-Stroustrup (@30:35)
- Herb Sutter http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/C-11-VC-11-and-Beyond (@37:40)