
Why should I have written ZeroMQ in C, not C++ (2012) - vq
http://250bpm.com/blog:4
======
jsolson
This actually sounds like an argument for why he should've used a smaller
subset (or different) of C++.

For example: make all constructors private and empty and use public static
factory methods for manufacturing new instances. Now the factory _can_ fail,
and return an error to boot.

Similarly, ditch C++ exceptions and introduce the notion of a status object
that methods return. You always have the option of C-style semantics without
dumping the genuine advantages C++ brings.

In terms of some of what is brought up in part II, a C++ engineer can (and
would) implement linked-lists C-style. See:
[http://www.boost.org/doc/libs/1_55_0/doc/html/intrusive/list...](http://www.boost.org/doc/libs/1_55_0/doc/html/intrusive/list.html)

~~~
rando3826
And so I will continue to avoid C++ because I'm not a master and will not
avoid the many pitfals, and I want to get things done, not become a master of
C++.

~~~
vitaut
Actually avoiding C++ pitfalls is not that difficult as most of them come from
C: manual memory management, macros, varargs, unsafe casts. In fact C++
provides safe alternatives to many of the unsafe C constructs, so if you stick
to them you are fine.

~~~
AlexandrB
C++ not only fails to hide the above-mentioned C pitfalls but adds entire
classes of pitfalls of its own. Just a few examples:

* undecidable grammar: [http://yosefk.com/c++fqa/defective.html#defect-2](http://yosefk.com/c++fqa/defective.html#defect-2)

* const inside containers: [http://yosefk.com/c++fqa/const.html#fqa-18.1](http://yosefk.com/c++fqa/const.html#fqa-18.1)

* template error messages [http://yosefk.com/c++fqa/templates.html#fqa-35.17](http://yosefk.com/c++fqa/templates.html#fqa-35.17)

C is often nicer than C++ because it's simpler: it only has the C pitfalls not
the combination of C & C++ pitfalls that plague C++.

~~~
vitaut
These are trivial issues compared to major safety and usability flaws
inherited from C that I mentioned. BTW the C grammar is not context-free
either. The compilation speed is mostly due to header system which is again a
C heritage, not because the C++ grammar is undecidable. And template error
messages are much better now in modern compilers such as Clang. As for the
const, its semantics is the same as in C.

~~~
the_why_of_y
The super-slow compilation speed is mostly because C++ encourages massive all-
inline template libraries like the standard C++ library and boost, which need
to be parsed and have the used templates instantiated for every compilation
unit.

Here's idiomatic hello world, with <iostream> vs. <stdio.h>

> g++ -E hello.cc | wc -l

17906

> gcc -E hello.c | wc -l

842

------
KayEss
This seems to be a failure to understand when exceptions and when error codes
are to be used. I'm just starting a big infrastructure project that needs to
be high uptime and distributed across a large number of machines, and I'm
doing it in C++ because I know I'd never get it working in anything else.

Let's take one specific example -- connecting to a remote host. The loop that
goes through the DNS returned IP numbers uses local error codes to try each in
turn until one is accepted. If none are accepted it throws an exception so
that the higher level application code can decide what to do about the connect
failure. The exception ensures that all resources are properly cleaned on the
way through.

The exception is used to guarantee that resources are properly de-allocated on
the way back up to where the error needs to handled as it cannot be handled
locally, probably not in the function that kicked off the connect attempt
either, but there's no local exceptions to handle the case where the error is
handled locally.

The use of the exception is more like a ROLLBACK on a database transaction
together with error reporting -- it helps ensure correctness by backing out
properly changes in state that were kicked off by the connect attempt. When
the exception is caught the program state is exactly as it was when the
connect attempt was first tried so we know we have good clean state to make
another attempt or to move on and do something else. Backing out all of these
other state changes is really hard when an error code needs to be handled non-
locally -- and it's this non-local handling of errors that's so hard and error
prone without exceptions.

So if you're only ever using error codes you'll far too easily make mistakes
when the error needs to be handled non-locally, and if you're only ever using
exceptions then it's going to be real ugly when the error is handled locally.

You need to use both if you want clean code that is going to work reliably (or
you need massive engineering resources to fix all of the bugs you'll end up
with).

~~~
bjourne
I think you are right about the author not understanding exception handling
very well. But your handling of exceptions is also incorrect. In a fault-
tolerant system, dns lookup failures or repeated connection attempt failures
is something you design for -- they are not exceptional events.

E.g you have function called connect() then it should return something like a
state object with a connection instance or an error indicator such as
{err:dns_fail, dns_servers:[..],hostname:".."}

Otoh, if you were writing a one-of script for scraping a web site then you
don't care about fault tolerance and then connection failures are exceptional
events so throwing on them becomes ok.

~~~
KayEss
I disagree -- the determinant is about state management and locality of error
handling and this is all about how your code is structured, not whether the
error is expected or not in some nebulous sense.

If you can handle the connect failure locally (i.e. probably no further away
then caller of the connect function) then by all means do so. If you find
another structure in your code makes other things simpler, but no longer
allows you locality of error handling you should use an exception.

Well designed libraries allow you to choose. Check out Boost.ASIO for an
example of this. Every API has both an exception and error handling version
and you're free to use whichever is most appropriate for what you're trying to
achieve.

This whole thing about 'expected' and 'unexpected' errors is a red herring
that leads people down the wrong path.

The exception backs you out of the transaction that your code is performing --
this is the way to think about it. An error code allows you to try something
else whilst keeping the transaction alive. Which you use depends not at all on
whether you think the error is "normal" or not.

~~~
bjourne
Well, the reason exceptions should be reserved for exceptional cases is
because they _are_ gotos. There is no way around that, a function that both
returns values and throws exceptions is more complex than one that doesn't
because you are jumping through call frames.

I once worked with a billing system that threw a BillingException when a
charge didn't go through. It works ok, as long as your only response to such
an error is to spew an error message to the user. But the more you need to
handle it, the less an exception makes sense. The code that tried to handle
the BillingException was a tangled mess of try and catch statemetns mixed with
retry loops. For example, if there was a temporary error at the payment
provider, you would just retry a few times, a permanent error, try with
another account, if the customers remaining funds were to low show an error
message or if they were just a few dollars of, charge them that and be happy
we got something from them.

On the other hand, the billing system could also throw a DbException in case
there was something wrong with the database connection. That's a different
kind of problem and fatal for a database-backed website. Nothing to do, except
log the error and crash.

The point of exceptions is not so much that you can catch and handle them, but
that it separates exceptional situations from in-band normal error handling.
Now what is in-band and what is exceptional depends on the circumstances which
is why, as you say, Boost.ASIO provides both variants. If your system is
supposed to handle the errors, use the one without exceptions. If not then use
the one with exceptions.

~~~
KayEss
> separates exceptional situations from in-band normal error handling

Of course if you redefine "exceptional" to mean out of band error handling
then we're in perfect agreement :)

I've also written billing systems and the worst possible thing that happens is
that you get stuck on a single customer due to some unforeseen circumstances
and can't go on to bill others. The ability to abort the in-code transaction
for that customer and move on to the next is exactly what you want from the
system, and the exception there makes that a far simpler thing to do (together
with RAII for clean up).

> function that both returns values and throws exceptions is more complex

Indeed, but it doesn't really matter because the clean up you get from
exceptions with RAII means that the correctness of the function is easier to
reason about and that's what you really care about.

------
vitaut
From my experience, if you need to write many exception handlers then you have
a problem in your design as you are using exceptions where you should have
used explicit control flow. There is nothing fundamentally wrong with the
exceptions and they are used in most modern languages, not only C++.

~~~
wvenable
I completely agree; the entire time I'm reading this I couldn't help but feel
this was misunderstanding the use of exceptions.

The stated design goal is that the project "never fail and never exhibit
undefined behaviour" yet exceptions are exactly for undefined behavior. If you
can't allocate memory, if your function is called with arguments that it
cannot possibly interpret, or that a fully expected network connection has
failed and cannot recover. Those are exceptions. For all your defined
behaviors, you can control just as described in the article without
exceptions.

There is clearly too many exceptions (and handlers) in the first case and way
too few in the second case.

------
Todd
Earlier comments:

[https://news.ycombinator.com/item?id=6220049](https://news.ycombinator.com/item?id=6220049)

[https://news.ycombinator.com/item?id=3953434](https://news.ycombinator.com/item?id=3953434)

~~~
iooi
And comments to part II
([http://www.250bpm.com/blog:8](http://www.250bpm.com/blog:8)):

[https://news.ycombinator.com/item?id=4455225](https://news.ycombinator.com/item?id=4455225)

------
derf_
These and many other pitfalls are all discussed in detail in the C++ FQA Lite:

[http://yosefk.com/c++fqa/exceptions.html#fqa-17.1](http://yosefk.com/c++fqa/exceptions.html#fqa-17.1)

[http://yosefk.com/c++fqa/ctors.html#fqa-10.17](http://yosefk.com/c++fqa/ctors.html#fqa-10.17)

[http://yosefk.com/c++fqa/exceptions.html#fqa-17.3](http://yosefk.com/c++fqa/exceptions.html#fqa-17.3)

The FQA helped my crystallize a lot of the reasons I hated C++. Even if you
like C++, it's a good idea to understand the FQA's arguments, for the same
reason people play devil's advocate.

~~~
Daishiman
And even so, a lot of very senior C++ users will disagree with the FQA's
recommendations.

~~~
MaulingMonkey
Even though I disagree with many of the FQA's recommendations, at least it
describes their rationale. Providing the arguments behind the alternative
viewpoint allows for an informed choice.

------
yason
The reason why the charm of C++ wore off on me early on and why C is like my
wife who I love more and more each year is that there's a lot of hidden
"knowledge" in C++. The language is always playing a game of its own and a lot
of your level of expertise depends on how well you've bothered to learn the
rules of that game.

For example, exceptions, as decribed in the article. Exceptions themselves
make sense in some scope: they model some cases of error handling and
controlled longjmping really well while are totally unsuited for other cases
where a more fine-grained control of failure is necessary. Thus, exceptions
themselves are applicable to an extent in a certain scope.

However, in C++, the scope where exceptions actually do work is even more
narrow than the scope where exceptions would generally fit. Like explained in
the article, the FQA, and this thread, there are numerous pitfalls where
exceptions totally break and a number of assumptions you have to make but
can't guarantee. This reduces down to a few "known almost safe ways" of using
them with a lot of "ifs" bundled in, and you need to explicitly know those
ways.

Unlike in C, in C++ you can't deduce what works and what doesn't out of merely
reading or writing code: you have to have enough experience to know these
pitfalls first hand so that you know how to avoid them and why. There's
basically a line drawn in sand of what is known to work and what is not, like
medical remedies in ancient cultures.

And further, all this effort is only really needed to deal with the language
itself. It's not required to solve the programming challenge at hand. It's
something that is not needed with C because you can reason with C on a "what I
see is what I have" basis.

~~~
pjmlp
> "what I see is what I have"

I guess you have not yet seen too much UB or compiler specific semantics then.

------
rumcajz
Author of the article here. Everything said in the article still applies IMO
but, to be fair, C misses some useful modern features, most importantly a
decent concurrency support. I've recently tried to remedy the problem by
implementing Go-style concurrency in C:
[http://libmill.org](http://libmill.org)

------
rkwasny
This might be unrelated, but after reading this 3 years after publication I
think that Go approach to error handling i so much better because of exactly
the same simplicyty as C.

( If you dont’t understand why, dont’t worry it will be obvious to you in 3
years :-)

~~~
voidlogic
>I think that Go approach to error handling i so much better because of
exactly the same simplicyty as C.

Not to mention Go having multiple return values IMHO also takes away the major
pain point of returning errors in C; using up your only return value and
having to take pointer arguments when you would rather not.

~~~
cjensen
Multiple return values is now handled in C++ with tuples and std::tie.

~~~
cjslep
While it works, it would be better if multiple return types were supported
natively by the C++ language precisely because of this:

    
    
        #ifdef _WIN32
        #    ifdef EXPORTING
        #        define DECL __declspec(dllexport)
        #        define STORAGE
        #    else
        #        define DECL __declspec(dllimport)
        #        define STORAGE extern
        #    endif
        #else
        #    define DECL
        #    define STORAGE
        #endif
    
        // For every return tuple type actually used:
        STORAGE template class DECL std::tuple<error_type, return_type1>;
        STORAGE template class DECL std::tuple<error_type, return_type2>;
        STORAGE template class DECL std::tuple<error_type, return_type3, return_type4>;
        // etc.
    

Even without the above, exposing STL across shared objects leads to the games
of "which side of the shared object did this get created" and "which STL did
this shared object link against". Losing these games leads to sizeable butt
pain.

~~~
maxlybbert
That's not a C++ issue, it's a Windows issue (which is why DECL and STORAGE
are defined to nothing on anything other than Windows).

~~~
cjslep
It's precisely a C++ issue because if multiple return types were natively
supported by the C++ language, none of this would be needed since no one would
be trying to export a STL object across shared object (or DLL) boundaries.

It's only the workaround (tuples) of this C++ issue that platform-specific
issues are allowed to arise, like Windows as you observed, which is why I view
the platform-specific issues as symptoms of a deeper problem.

~~~
maxlybbert
I may have misunderstood your terms, so I apologize if my response doesn't
cover what you're talking about.

> if multiple return types were natively supported by the C++ language, none
> of this would be needed since no one would be trying to export a STL object
> across shared object (or DLL) boundaries.

I believe you're talking about C++ export. Everybody will say that export was
too complicated to implement; although (1) the full sentence should be "too
complicated to implement, using current linkers," and (2) both CFront and EDG
implemented _did_ implement export using current linkers CFront implemented it
in the early '90s), so the argument that it can't be implemented is simply
wrong. It is accurate to say "using current linkers, refusing to use
nonportable linker features, and _following the Borland template instantiation
model_ ( [https://gcc.gnu.org/onlinedocs/gcc/Template-
Instantiation.ht...](https://gcc.gnu.org/onlinedocs/gcc/Template-
Instantiation.html) ), it is hard to implement export; only EDG ever paid the
price to do so."

But the C macros to #declare dllimport/dllexport (which is what I thought your
original complaint was) are meant to solve a Windows linking issue that
plagues C as much as it plagues C++. And, to be honest, it's a link time
optimization that non-Microsoft linkers don't appear to have trouble with.

------
talles
Exception throwing is the goto of 21st century. The article's examples aren't
the worst, it's common seeing exceptions for business rules in Java/.NET land.

~~~
est
Wat? I thought callbacks are the goto of 21st century.

~~~
santaclaus
I thought plain threads are the goto of the 21st century?

------
kabdib
I like Google's guidelines for writing C++. I've worked in a number of groups
that independently came up with just about the same subset of C++ (and same
set of styles), so those guidelines aren't totally off the wall.

~~~
helmut_hed
The Google guidelines are... somewhat out of sync... with the intended use of
language features as described in the Standard and the recommendations of
people associated with it. They seem to take as a starting assumption that the
language is misdesigned and major parts of it should simply be avoided or used
in unintended ways. If you agree, it's a perfectly fine guide. Otherwise, I'd
recommend reading any of Scott Meyers' or Herb Sutter's books and following
their advice.

~~~
kabdib
I've read those books. I have a lot of respect for the authors, and they have
a lot of good advice.

But _Exceptional C++_ made me not want to deal with C++ exceptions, ever. If
getting C++ code correct in the face of exceptions is that bloody hard, then
the language _is_ broken. I'm convinced that cobbling up an "int" equivalent
type that is exception safe is a pyrrhic endeavor that will be a continual
source of fragility in production code.

Alexandrescu's _Modern C++ Design_ made me want to forget that templates ever
existed. The entire second half of that book I kept saying to myself, "If I'm
on a project with someone actually checking stuff like that in, one of us is
going to have to leave." Fortunately I've yet to run into those kinds of
template metaprogramming games in the wild. Good programmers seem to abhor
them.

[Edit: Actually, I remember a bunch of stuff in Modern COM that used templates
very heavily. Essentially impervious to debugging; that stuff sucked hard]

More recent C++ design decisions are better than the ones the committees made
in the 90s and early 2000s. But the successful teams I've seen all practice
restraint and keep things debuggable by limiting the amount of magic going on
under the hood, regardless of what the committees have been pushing.

The Google C++ standards (and others that were independently created and
haven't been seen much outside their own development cultures) may be "dated",
but they remain an indictment of the design of C++.

~~~
pjmlp
> If getting C++ code correct in the face of exceptions is that bloody hard,
> then the language is broken.

Blame C, as they are that way to keep C++ exception behaviors compatible with
C semantics.

------
TwoBit
As usual, this guy doesn't understand how to use C++. Nobody with any
significant C++ experience puts error generating code in constructors. Once I
saw that I stopped reading.

~~~
Mikhail_Edoshin
How does a proper C++ constructor report errors?

~~~
maddening
Several solutions.

First I can think of: don't create situations where error can happen. Compile
with exceptions turned off. Out of memory? Terminate the process/thread.

Another: create your objects using some factory, make constructor empty and
initialize class by said factory once instance is already created (perhaps
recursively instantiating its members). This way all error generating code
will be moved outside of constructor.

See how Google approach it: [https://google-
styleguide.googlecode.com/svn/trunk/cppguide....](https://google-
styleguide.googlecode.com/svn/trunk/cppguide.html#Doing_Work_in_Constructors)

~~~
helmut_hed
Compiling with exceptions turned off is highly unusual. If you do that, you're
not really writing C++ anymore, as it's a fundamental part of the language.
Exceptions are the mechanism provided for indicating that it was impossible to
construct an object, and to trigger appropriate cleanup actions.

~~~
evincarofautumn
> Compiling with exceptions turned off is highly unusual.

I thought it was routine. Until C++11 made smart pointers standard, it was
unreasonably difficult to write exception-safe code, so my understanding was
that a lot of C++03 code didn’t even try. Game developers would routinely use
-fno-exceptions and -fno-rtti for reliability and performance reasons.

------
zvrba
I'm a professional, C++ dev and my greatest quibble with C++ is its deranged
system of numerical types, implicit conversions between them, and that signed
arithmetic can produce UB just about anytime:
[http://www.boost.org/doc/libs/1_55_0/libs/numeric/conversion...](http://www.boost.org/doc/libs/1_55_0/libs/numeric/conversion/doc/html/boost_numericconversion/definitions.html)

~~~
pjmlp
Mostly inherited from C, actually.

~~~
zvrba
Well, "defect by design" is still a defect and I have to work around it on
almost daily basis. Besides, being able to do (bool+int) could not come from C
since C didn't have bool at the time.

EDIT: actually, there are two defects here. One are the implicit numeric
conversions. The other one, which causes me most grievance in this context, is
that size() on containers returns an unsigned type.

~~~
pjmlp
> Besides, being able to do (bool+int) could not come from C since C didn't
> have bool at the time.

Except that

    
    
        typedef int bool; /* or BOOL */
        #define FALSE 0
        #define TRUE (!(FALSE))
    

used to be quite common back then. So C++ only adopted an existing idiom in
the C community.

~~~
zvrba
Is there any production-level C code which relies on (int+bool, and other
semantically meaningless arithmetic) working?

Besides, C++ broke nevertheless broke C compatibility because ++bool will
never increment the value past 1, and bool=int will coerce int to 0 or 1. C
will preserve the original values.

~~~
pjmlp
> Is there any production-level C code which relies on (int+bool, and other
> semantically meaningless arithmetic) working?

Yes. I have seen enterprise code making interesting tricks to covert handle
values into pointers, back in the late 90's.

~~~
the_why_of_y
Code that switches over a variable nominally declared to be of type BOOL and
handles more than 2 distinct value cases is always such a joy to read; every
enterprise grade code base should have at least one of these for entertainment
purposes.

------
nickpsecurity
Interesting article. So, more confirmation C++ is inferior for writing
predictable or high performance applications. I actually see an opportunity
for a C-compatible or C-competitive systems language here to beat C++ at its
own game. Zero-cost abstractions plus solving C++'s specific problems,
painless C FFI, and compilation to C (or LLVM) to leverage their compilers.
Might make a nice combo. I doubt we'll see much takeup, though, as the
technical merits rarely dictate that.

On a related note, I'd like to see the results of it being coded in a modern
Ada or SPARK with one of the best compilers. I'd be interested in seeing it
with full runtime checks on and off with conservative optimization. ZeroMQ
with strong implementation assurance could lay the foundation for all kinds of
secure networking and middleware schemes. Heck, I'd like to see a team
formally verify a version of it with an executable for common architectures.
I'd have fun with that. :)

~~~
neverartful
Objective-C: "You messaged?"

I believe that Objective C is a pretty good (yes, it's getting old)
combination of C compatibility with object oriented features as well. The 2
big downsides IMO are: (1) essentially restricted to Apple ecosystem and (2)
the syntax keeps a lot of developers away.

~~~
nickpsecurity
My quick perusal of online links show that Objective-C is slower than C++,
maybe due to its dynamic typing. It has the C compatibility and basic OOP.
Yet, my idea was a language like this which is _faster_ than C++. Maybe a
statically typed version of Objective-C would do?

~~~
neverartful
The OO parts of Objective-C will likely be slower than the OO parts of C++ in
many cases. There are a few places where ObjC can help even the score on
performance. Typically ObjC objects are not copied (cloned). Normal usage
(within a collection, ivars, etc.) is with reference counting. Prior to C++11
(shared_ptr, unique_ptr, etc.), there wasn't a good way within the language
itself to track object ownership without having to make object copies.

Since C++ and ObjC are both (essentially) supersets of C, you can always mix
however much (or little) of plain C that you want. Everything about OO in ObjC
is very dynamic. It allows you to do some very cool/flexible/unsafe/unwise
things.

In my opinion, that where the tradeoffs start showing up. Flexibility, runtime
speed, nice abstractions, development speed, code complexity, etc. Really
difficult, if not impossible, to have them all at once.

Despite the unusual looking OO syntax of ObjC, overall it's a pretty simple
language given the power it provides. Once you get acclimated to looking at
its syntax, I'll even dare say that it's much easier to read and understand
than many other languages.

~~~
nickpsecurity
I appreciate the detailed reply. It seems like it's not a fit for my zero-
overhead strategy but an interesting alternative to C++ for app development.

------
shepardrtc
I used to think the same way. In fact, I would reference this article to
reinforce my beliefs. But then I actually took the time to relearn C++14 the
right way using Stroustrup's Programming Principles book. A project that was
horrific in C became ridiculously easy in C++. And I didn't use a single
malloc.

------
clumsysmurf
Sometime in the future:

"Why I should have written nanomsg in Rust, not C".

~~~
cesarb
If you read part II (the link to it is near the end of the post), you see that
one of the things he wanted was intrusive linked lists.

From what I have read so far, implementing intrusive lists is not easy in
Rust. (If you know of a working intrusive doubly linked list implementation in
Rust 1.0 that does not invoke undefined behavior, please tell me; I'd like to
know how it should be done.)

~~~
dschatz
I have one here:
[https://github.com/dschatzberg/intrusive](https://github.com/dschatzberg/intrusive)

It can be used in a freestanding (nostd) environment such as a kernel. It uses
unsafe code but provides a safe interface. The primary technique is to embed
the type that is iterated over in a larger struct which contains the links.
This way I can give out references to the inner type without fear of
invalidating the iterator.

~~~
detrino
How is it possible to write a no-allocation intrusive linked list without move
constructors ? When you move one of the nodes (or sentinel if you use one) it
would invalidate the links in other nodes.

~~~
dschatz
The list takes an OwningPointer to the object to be inserted. One of the
requirements of this trait (which is unsafe to implement) is "the object
cannot be moved while in the `LinkedList`." So Box would work, or a mutable
reference would as well (the library implements the trait for these types
already). The list itself doesn't do the allocation though.

------
cpp098
C++ is bad choice for ZeroMQ? Only after he rewrite the code in pure C and run
it for years, his conclusion will be persuasive, isn't it?

------
alexnewman
He means rust

