

Rule of Zero (C++) - AndreyKarpov
http://rmartinho.github.com/cxx11/2012/08/15/rule-of-zero.html

======
shin_lao
It's nice to see more and more C++ articles.

Some additions:

\- The least you allocate on the heap, the better. Move semantics really help
in that aspect;

\- Smart pointers are not magic. Although I really think using unique_ptr
whenever possible is a good idea, shared_ptr comes with a performance cost
(especially in multithreaded environments), can introduce vicious memory leaks
and is often used as an excuse to not think your lifetime cycle through.

~~~
pjmlp
I also like it.

C++ suffered for a long time due to lack of quality libraries.

This now seems to be finally changing, at least until another language is a
good enough replacement for it.

I am keeping an eye how D and Rust evolve, additionally Ada seems to be
getting some users, at least here in Europe.

~~~
mhd
Ada in Europe? Interesting, where did you encounter that?

As for quality libraries, I'd almost say that we had too much of them. For
every platform and every different C++ methodology/subset (from "C with
Classes, ocassionally Structs" to "Post-Modern Template Abuse") had its own
set of supporting libraries. Often good enough, but most of the time not the
ideal source for _your_ particular project, and very hard to integrate, if
every code paragraph follows a different paradigm, depending on what library
your calling.

Still don't see a good solution to that. Quite likely that your GUI library
won't quite mesh with your collections, data parsing or network support.

~~~
pjmlp
> Ada in Europe? Interesting, where did you encounter that?

I heard it at this year FOSDEM.

Since GNAT availability many universities seem to be making use of it. As for
the industry I guess it still constrained to the usual types of systems where
Human life is very important.

As for the libraries you're doomed to have that for all libraries that are not
part of the standard library. Even the "comes with batteries" languages suffer
from this when you need to integrate external libraries.

If Java and .NET didn't happen, maybe there would already be an InteliJ for
C++. I am looking forward for the tooling progresses made possible by using
the compiler as plugins, as clang shows.

~~~
mhd
Well, if you've got a pretty big standard library, you've got more on which to
base external libraries on, regarding the style of the code. The STL alone
didn't really provide a big enough template. Sometimes it even went the
opposite way, so when you read something in lower-case letters with
underscores, you knew that you were using the "core C++ language", putting
things like "vector" and "reverse" closer to keywords than library APIs.

Especially if the rest of the code base was the usual OO class forest.

I knew of Ada use in teaching, it seems to be quite popular for some advanced
"software engineering" courses. Then again, I've seen a pretty great amount of
Algol-family languages there, out of proportion to the actual practical use.
Oberon was quite popular for a while, as it was a very small language to teach
and the barriers to running your first programs were basically non-existant
(as you can easily call your module function directly, compared to the rather
perplexing "public static void main" of introductory Java courses).

------
huhtenberg
Abstracting the ownership behind an interface presumably is needed when the
ownership model is very complex. From my experience this is nothing but a big
red flag. If you need a class to manage an ownership, it means that you can't
easily trace its movement, in which case it means that it is a total bitch to
debug, leave alone being able to read through the code and understand where
that damn ownership is at any given moment.

I know it's beautiful language with a laundry list of features, but keeping
things simple is still the best approach:

    
    
      #define __no_copying(type)                      \
                                                      \
    	private:                                  \
    		type(const type &);               \
    		type & operator = (const type &); \
    	public:
    
      class foo
      {
      public:
            foo();
            __no_copying(foo);
      ...
      }
    

In a spaghetti-less code this typically applies in 99% of all cases. There's a
remaining 1%, which it's easier and cleaner to handle on a case by case basis.

~~~
gregsq
Agree in the main. Even before smart_ptr's were integrated into the standard,
bespoke versions that appeared to some to remove scoping confusion had a
tendency to propagate like weeds. In the majority of cases, const correctness,
mutability if needed and good scoping concepts removed redundant reference
counting run time garbage.

On one project I worked on as a contractor, and quite a large one at that, I
removed every single one of them and rescoped the code base. It took a while
but performance alone improved by about 400%

~~~
rmartinho
I'll say this just once: smart pointers and reference counting are not the
same thing, and neither of them implies the other.

~~~
gregsq
Didn't need to say it at all. I probably wasn't being clear enough but I was
referring to early versions that were called smart precisely because they
removed the need for the programmer to count because they counted themselves.
Hence 'smart'. Before boost and others cleaned it all up.

As others are pointing out they have advantages and disadvantages. It all
depends on the situation. But they're certainly not free because they have a
scope cost. Code size and simplifying compiler optimisation are important too.
I think it's worth that pointing out.

------
radarsat1
The codebase I'm currently working on has quite a few bugs related to
ownership. However, the programmers I'm working with are very capable and all
are familiar with different kinds of smart pointers in the STL. Unfortunately
they are either, 1) typedef'd to the point where it's hard to tell what kind
of ownership is used where, 2) often can't be used because objects need to be
passed by reference to libraries that use regular-old-pointers.

I think many bugs could have been avoided by not typedef'ing our pointers,
because when you have to type `std::shared_ptr<myclass>`, then you know what
kind of pointer it is. On the other hand, if you have to, at least call the
typedef `myclassSharedPtr` instead of `myclassPtr`. But for (2), I don't see a
work-around. How can you write good code when your critical dependencies don't
play nice?

~~~
rmartinho
> often can't be used because objects need to be passed by reference to
> libraries that use regular-old-pointers.

I don't understand what you mean by this. If you have a `smart_ptr<T> p;`
nothing prevents you from passing `*p` or `p.get()` to some legacy interface.
You only have trouble if that legacy interface wants to claim ownership of the
object.

~~~
ben0x539
I think the issue is that the typedef is intended to hide the smartptrness so
it's not obvious what the API for getting an old-style pointer is, or whether
getting an old-style pointer is even part of the intended public interface
anymore (beyond p.operator->() anyway ;)

------
ambrop7
To me, "Rule of Zero" sounds more like not implementing {con,copy-
con,de}structors, i.e. forgoing RAII. I've been doing this quite a lot, and it
makes writing low-level and optimized code so easier.

Actually, I find that having fixed ownership makes much more sense than
reference counting for the majority of cases. Example: we have a FileSystem,
and a bunch of File's belonging to this FileSystem. Whenever a part of a
program needs a file, it just initializes _its own_ File object, and when it
doesn't need it anymore, it releases it somehow (e.g. file.deinit() or
destructor if RAII is used). Even if two users both need the same file, each
gets its own File. The FileSystem internal implementation can take care of any
resources sharing, invisible to the users.

As far as the memory management of FileSystem is concerned: again, in most
cases, you don't need shared_ptr<FileSystem>. Just make sure the FileSystem
stays alive as long as there's the possibility that anyone is using it. Like
initialize it at the beginning of the program, and deinitialize it during
shutdown at the right point.

~~~
mpyne
This "Rule of Zero" construct doesn't forgo RAII, since RAII does not simply
mean "write constructors and destructors".

Instead it allows the compiler and language to automatically do the right
thing to enable RAII by using appropriate library-provided building blocks.
E.g. his std::unique_ptr + custom deleter type enables the compiler to
automatically make a RAII-compliant class for the author, so it's still RAII,
just automatically implemented (except for the ctor, in this case).

------
jamesaguilar
I like that using technique, but why is it using and not typedef? I thought
using was meant for exposing names from superclasses.

~~~
Sharlin
In C++11 you can write "typedef templates". However, as "typedef" has always
been a misleading keyword (it does not define a new type, it declares an alias
for an existing type) it was decided to instead overload the using keyword:

    
    
        template <typename T>
        using MyVector = std::vector<T, MyAlloc>;
    

As a side effect, it is now also possible to use using as a 1:1 replacement
for typedef:

    
    
        // strictly equivalent to typedef int MyInt;
        using MyInt = int;

~~~
AlexandrB
After reading the C++ FAQ on this I can accept that using tyepdef may have
been impossible/confusing, but why overload "using"?

This is one of the things that makes C++ mega-complicated. The same keywords
have different meanings based on context, when really a new keyword should
just have been used. For example "virtual" functions vs "virtual" inheritance.

~~~
skrebbel
The moment you add a keyword to a popular language, you break lots of existing
code that happen to have that keyword as an identifier, e.g. as a variable,
class or function. This means that when adding features that need a new
keyword, you best overload an existing one.

It's for this reason, too, that Java 7 overloaded `try` to implement some sort
of poor-man's-RAII-support, instead of adding a new keyword (such as C#'s
`using`, which does the same, and was by the way also overloaded for the same
reason)

------
Strilanc
There's a similar rule of thumb in .Net with respect to managing native
resources. Inlining the logic that handles the lifetime is much more bug prone
than encapsulating that logic into a wrapper class (e.g. SafeHandle
[http://msdn.microsoft.com/en-
us/library/system.runtime.inter...](http://msdn.microsoft.com/en-
us/library/system.runtime.interopservices.safehandle.aspx) does this).

~~~
Flow
I guess I'm lucky enough to never have seen that class in code I've seen. :)

That seems like typical Microsoft object-orientation. It's not really much of
encapsulation. Reminds me of MFC :-/

~~~
pjmlp
Actually Microsoft started by developing a C++ library with lots of OO in the
design.

However not everything went as they expected and people were also demanding
something more light over Win16 and MFC was born.

[http://computer-programming-
forum.com/82-mfc/d13ea80282846f9...](http://computer-programming-
forum.com/82-mfc/d13ea80282846f9f.htm)

------
davvid
Nice article, but the use of std::wstring threw me in a little loop. Perhaps
the author is on Windows where its usage is more common?

~~~
spoondan
Per the article, "As an example, let’s write a class that owns a HMODULE
resource from the Windows API."

------
andrewcooke
cache
[http://webcache.googleusercontent.com/search?q=cache:8r4wIEj...](http://webcache.googleusercontent.com/search?q=cache:8r4wIEjdtwYJ:rmartinho.github.com/2012/08/15/rule-
of-zero.html+Rule+of+Zero&cd=1&hl=en&ct=clnk&gl=cl)

------
rwmj
Do people who write C++ also manually allocate disk blocks instead of using
the filesystem? Just use a garbage collector. These problems will all go away.

~~~
Patient0
The problems start when you need to manage resources other than memory.

Examples of things that require explicit clean up: -> Files -> Sockets ->
Transactions

The C++ resource model can be applied to all resources, not just memory.

Garbage collection only "solves" memory management for you - often that's the
least important resource that your program is managing.

~~~
orlandu63
You don't need RAII to manage non-memory resources. All you need are first
class functions.

~~~
anonymousDan
Could you expand on this a bit? I'd be interested to see an example of what
you mean.

~~~
zem
it's a ubiquitous pattern in ruby. this blog post uses a file open/close
lifecycle manager as an example: <http://yehudakatz.com/2012/01/10/javascript-
needs-blocks/>

~~~
Evbn
That's more about coroutines than first class functions, and it is RAII, using
a wrapper methodd for the actor/dtor instead of defining a class.

~~~
zem
it is not raii as i understand the term. there is no destructor involved in
closing the file, there is just a function that opens the file, passes your
code the handle, then closes it when you're done.

~~~
bowyakka
Doesn't this amount to the same thing ?

~~~
zem
it accomplishes the same purpose, but the filehandle gets closed explicitly,
not when it goes out of scope and its destructor gets called.

