Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Why I should have written ZeroMQ in C, not C++ (2012) (250bpm.com)
108 points by babawere on Aug 15, 2013 | hide | past | favorite | 119 comments


Previous discussion with 207 comments

https://news.ycombinator.com/item?id=3953434

Then, the original author wrote a follow-up which had another ton of comments on his blog and another 162 comments on HN.

https://news.ycombinator.com/item?id=4455225

I wonder if anything new can be said here.


Most importantly can go lang really take the place of C++


In this situation, Rust seems more likely. Go may have sacrificed the absolute top end of performance by making GC an unavoidable memory management mechanism. You have a variety of tools to manage garbage generation with careful programming which aren't necessarily obvious on a casual reading of the spec, but AFAIK you can't actually fully escape the stop-the-world GC. It is not clear to me that Go will ever escape stop-the-world, either. There's still some room for it to get better, but the Erlang-esque "per process heap" will probably never be available, or at least, not for a long time and only when writing your code in a way the optimizer can understand.

Still, you will be able to do "pretty darned well" in Go. And there's a lot of use cases for which that will be indistinguishable from the "absolute top end".


No, a garbage-collected language cannot replace C++. Explicit control of memory and deterministic operation is an important tool for some types of software e.g. high-performance server software.


Azul has made a JVM with predictable GC.

http://www.azulsystems.com/technology/java-garbage-collectio...

So yes, it is possible to have pauseless GC. That won't fit all problems that C++ is used for (like embedded), but it certainly hits on the server software side of things.


To run it on x86, you need a heavily modified Linux kernel. You also sacrifice about 30% or so of your throughput on the extra operations that allow for the dramatically reduced latency, which may not be acceptable for some people.


I do not see how this solves the server software side of things. Most high performance server software (1) has precisely one process/thread per core and (2) takes complete control of its memory management. These behaviors have substantial performance benefits. A GC thread of any type will bleed performance, and that is ignoring the loss of performance from poor memory and cache efficiency that is inherent with GCs. High performance server code is one of those areas that highlights where GCs are inappropriate. I've written quite a few server engines in GC languages; you expect to see about half the performance of C/C++ as a rule of thumb.

Most modern server software performance is significantly bound by memory bandwidth and latency. Introducing inefficiencies in this area tends to result in material degradation of software throughput. In fact, this is the only substantial advantage of C/C++; it can be efficient with memory access, caching, and utilization in a way that no GC language can be, and these are major contributing factors to performance.


The notion that poor memory cache efficiency is inherent with GC's is not terribly well founded. Copying collectors (like the one the HotSpot VM uses for Eden Space) tend to have great cache efficiency as compared to typical use of the C heap, where you can't relocate chunks of correlated memory together. Even mark-and-sweep collectors can do quite well for themselves.

I'd actually say quite the opposite of what you are saying: in a server environment, you often care more about throughput and mean latency (as opposed to 99th %-ile latency), and GC can really do great there. What's nice is that GC allows you to have near zero cost heap allocations and amortizes the cost of cleanup. What tends to be the problem is a lot of languages with GC make it difficult to efficiently work with the systems's buffer cache, so you typically see problems with systems that do disk I/O with large amounts of data.

I've written a LOT of C/C++ & JVM based servers. Particularly for large projects with a lot of independent moving parts and object lifecycles that are not tightly tied to sessions, the JVM based servers are remarkably competitive if they aren't disk IO bound.

There's a reason why a lot of HFT firms use Java...

C/C++ still kicks butt for tightly written servers with fairly fixed functions, and generally for anything that does tons of disk IO (though the Cassandra guys have, as an example, demonstrated one can do quite well if you take the time to get it right), and of course anything with unsigned arithmetic (seriously, when are we going to acknowledge that this was a bad idea?).


> The notion that poor memory cache efficiency is inherent with GC's is not terribly well founded.

It may not be "inherent" to GC, but in practice every GC that I'm aware of has terrible cache efficiency compared to manual memory management.

> Copying collectors (like the one the HotSpot VM uses for Eden Space) tend to have great cache efficiency as compared to typical use of the C heap

False comparison. The real test is against high performance memory techniques (like memory pools, which are relatively easy and common in practice if you care at all about cache efficiency) not "typical use of the C heap." When you actually compare GC vs high performance manual memory management, manual memory management is significantly faster.


> False comparison. The real test is against high performance memory techniques (like memory pools, which are relatively easy and common in practice if you care at all about cache efficiency) not "typical use of the C heap." When you actually compare GC vs high performance manual memory management, manual memory management is significantly faster.

That's not a false comparison. They represent about equal development effort (well, the GC version is likely still less effort).

Sure, if you spend a ton of time optimizing your memory usage for your specific needs you will do better than a general purpose algorithm (though it is remarkable how often those solutions often implement their own forms of automatic memory management tuned for their needs). If it turns out that you couldn't do that, it'd be awfully hard to collect a paycheck (and in fact most programmers could not collect that paycheck).

Of course, even that effort would not measure up as compared to an omniscient GC algorithm (and the paycheck for building that would be even bigger ;-). What is the point?

GC's do well. When you really need to tune memory usage, you get in there and do the work, and you and do that in GC'd runtimes too. A good memory manager will, for common workloads, start you off in a better place and make the work of tuning easier. Certainly there are cases where it won't be so helpful, but that doesn't mean they are "bad" at memory cache efficiency.


And no2, a language which can not be consumed by others can not replace C++. Because of the runtime requirements (both GC and goroutines), it is not possible to use a Go library through an FFI, you have to embed a go program instead as you do Python or Lua.


I'm not sure there's any reason that garbage collecting and manual memory management can't exist in the same programming language. I can't think of any where they do, but it seems like you should be able to assign memory manually and have that as an exception. :/


C# supports manual memory management (in "unsafe" blocks). They don't coexist very well because a standard library written for manual memory management looks rather different than one written for GC.


Rust supports both manual memory management and GC.


Nonsense. Most languages that include garbage collection allow you to turn it off; the same languages also allow you to explicitly control memory.


>Nonsense. Most languages that include garbage collection allow you to turn it off; the same languages also allow you to explicitly control memory.

BS. Most languages don't allow you to turn off their GC, except if by it you mean doing silly tricks and jumping through hoops, using tons of memory to avoid having to GC, using pools of pre-allocated objects, not using any standard libs, etc. Even then, they are not callable from outside code (like C is and Rust tried to be) and memory use is huge.


> Rust tried to be

"Tried"? AIUI, every part of Rust is a work-in-progress.


Yes, that should be "tries". I meant the experiment with turning off the GC some months back.


> Most languages that include garbage collection allow you to turn it off

Hmm. Clearly I don't know much about this. How do you turn off garbage collection in Java and (say) Python?


Can't say for python but in Java off heap steuctures are pretty standard for performance critical components.


Interesting. Are there APIs to do this?


Python call gc.disable(). I'm not aware of any way to do it in Java other than raising the memory to available to such a high level that it doesn't become an issue (which would probably only work with processes that died, as opposed to long running programs).


gc.disable() introduces an unavoidable memory leak in my program. I also can't control the memory usage of the libraries I link against.


Not likely because things like gc makes interoperability with other languages difficult.

Pretty much every language can interoperate with C.


Yes, please.


Library of data structures and algorithms (STL) is part of the language. With C I would have to either depend on a 3rd party library or had to write basic algorithms of my own in 1970's manner.

Broken record here: there is a middle ground between importing a "3rd party" library (usually this means GLib) and writing your own Red-black tree: you can just port a lot of the STL back to C. I did that 10 years ago to get a workable straight-C RB tree by specializing STLPort's <map> on void-star. It's been tremendously helpful.

Another idea that more C code should adopt from STL is <vector> --- making a thin wrapper around resizable arrays the go-to container, rather than linked lists which are the de-facto go-to today.


> With C I would have to either depend on a 3rd party library

I can never quite get past this one. There seems to be this fantasy that you can write your code without any kind of dependencies whatsoever, and that the user can just drop it into their environment and have it work. Here's what ZeroMQ says about how to install their software:

1. Make sure that libtool, autoconf, automake are installed. 2. Check whether uuid-dev package, uuid/e2fsprogs RPM or equivalent on your system is installed. 3. Unpack the .tar.gz source archive. 4. Run ./configure, followed by make. 5. To install ØMQ system-wide run sudo make install. 6. On Linux, run sudo ldconfig after installing ØMQ.

How is depending on the presence of a massive build system, a set of file system utilities, and a compression utility different from depending on a "3rd party" library that contains an implementation of an RB tree?


How is depending on the presence of... a compression utility different from depending on a "3rd party" library that contains an implementation of an RB tree?

The other nits are fair, but this one is not. You mean gzip and tar? Even in whatever hell has banned these tools, it is possible to develop in both C and C++. Downloads just take longer.


OK, so you need some stuff to build.

The point is the runtime dependencies are small.

It also requires a compiler to build. Where's your rant about that?

(I keep promising myself to stop reading HN threads about C and C++ due to so much inanity. Looks like I caved.)


> The point is the runtime dependencies are small.

Is that the point? Because that makes absolutely no sense to me at all in a world of modern linkers, shared libraries, etc.

My assumption is that the OP was concerned about reducing the user's dependency list. And on that score, yes: I could have added a compiler to my list.


If "the user" is compiling code then yes, that is a dependency for them or their package manager/ports system to track. However many times the user installs a binary and never needs the source.

The other thing, I think this highlights one difference between C programmers and others, and maybe explains our disagreement. People coding in C often care a lot about the object code. It's not all that uncommon to look at the assembly and question what your compiled has done and make tweaks to get the binary looking better. Hence the disconnect between me saying runtime dependencies are the most important thing and you saying you don't want to install autotools to build. To me the latter complaint comes from a completely different planet.


I think you're making it too complex (or else I am). I think the OP is making a kind of "batteries included" argument (a la Python) -- that is to say, "it's better when the language has everything built in, as opposed to farming things off to external, 3rd party libraries."

All's I'm saying is that that seems to me pretty silly. It can't be about reducing the user's burden, because you're going to burden the user anyway with all kinds of toolchain dependencies. It can't be about the runtime, because the user will, except in rare circumstances, hardly notice; if they have autotools, they have glib.

I can think of million reasons not to use C. But, "Well, I don't know. I'd have to use a third party library. . ." Really?


A lot of third party libraries are just header files you can put into your application directly. How is this even worth complaining about?


Actually, I wish C had support for templates. It doesn't seem like templates go against C's core philosophy, and not having to cast void*'s back and forth would be nice.


You don't have to cast void * back and forth to anything.

   void *p = /* ... */;
   char *q = p;
Despite what the C++ weenies will say this is valid C! Please do not commit this cardinal sin:

   char *p = (char*)malloc(n);
No no no! No need to cast that! Please just write:

   char *p = malloc(n);
And if somebody tells you that won't compile without an explicit cast, please kindly remind them what language you are writing.

[Actually I agree with you that C++ templates are really nice, chiefly for the reason that they allow you to avoid function pointers. In C++ you can pass your "callback" to a template and it gets inlined right with the body of the function.]


Very true. It's my biggest annoyance with Visual Studio when I write straight C code that the intellisense error checker moans about this.

The compiler is of course perfectly happy when it's running in C mode. But then I'm still upset about <stdbool.h> it should get off my damn lawn.


For a while I thought C + templates would be a great idea as well. In theory templates are a much more powerful and expressive way of generating code than macros/xmacros.

However after playing around with the idea in a few C++ modules I was writing I came to the conclusion that it's just not worth it. Part of what makes C so enjoyable to write is its relatively "weak" typing and the flexibility that comes with that, primarily freely casting between pointer types including void*'s where necessary. Templates fight against that at every opportunity and I found myself spending more time trying to figure out how to wrangle my code into the template type system than actually making progress on the problem at hand. Newer additions like C++11 constexprs take that type-system frustration to an entirely new level.


> I wish C had support for templates

Generics. Polymorphic functions and collections don't require the complexity of C++ templates.


C sort-of-does have a template facility through macros. Some things about it are awkward - e.g., if you need to generate identifiers (which is traditionally done with __LINE__, which is not safe) - but it can mostly give you what templates do, if you know what you are doing.


Specializing a STL container to void-star is a far cry from what you get with the STL templates. You lose type safety (casts everywhere) and give up the nice value semantics of C/C++ (no vector<some_struct>).

Side note: red-black trees are actually pretty terrible on modern hardware for the same reason linked lists are, b-trees are usually a better alternative.


Obligatory Sean Barrett reference: http://nothings.org/stb/stretchy_buffer.txt - (though, see the warning on http://nothings.org/ about strict aliasing optimizations)


Why does this code have to be so ugly :'( I'm sure it's useful, but I could never bring myself to use "that".


Have you ever looked at the STL internal implementation of <vector>?

Different strokes for different folks, but ... I think stb arrays are beautiful in comparison.


Most of the ugliness in stdlibc++ comes from the ugly ass naming conventions that are (or were) inherited to prevent conflicts. The quality of implementation in the STL is fairly high, if a bit old.


The conclusion I get from the article, the comments, and the other threads, is this:

C++ is not a better C.

This was a really common attitude about a decade ago: don't use C for anything, just point cpp at your C code and use whatever random collection of C++ features you think make your life easier. We've learned since then that that's a recipe for disaster, and you should either write a C program in C (and save yourself the heartache) or embrace the fact that C++ is an entirely different language.

The author is clearly committed to writing a C program and doing that in any language other than C is a mistake.


Unfortunately, you can't draw any conclusions about C++ from the article.

a. The article isn't really about C++ in any general sense.

b. The author fails to learn the solutions that are commonly used in C++ to solve his problems so auhtor fails to teach the reader anything

The article is about a couple specific design pitfalls that occur when using C++ exceptions and how the author never solved them. The only conclusion is avoid the design anti-patterns the author used.

So what anti-patterns did he use? The author used exceptions (an optional language feature best used for abort handling of RAII operations) to handle return codes from paths inside a function (which is usually handled with error codes, not exceptions). Not a real problem except that the author failed to carry sufficient context through to the catch location and therefore couldn't clean up (although clean up should have been handled by RAII destructors, catch should only ever need to clean up "this", which should know its own state). Author also failed to catch with sufficient locality and hence made his code confusing.

Instead of realising that exceptions weren't well suited to error code handling and he wasn't throwing enough context information and he should have been using RAII anyway, the author wrote off exceptions for all tasks – including constructors (which is a poor choice since it eliminates all possibility of RAII from the program). This can still work but then the author couldn't work out how to write a factory method that potentially returns an error code instead of an object and instead juggled the ugly state problem of extant but invalid objects. Author could simply have returned a boost::optional<my_class_which_can_fail_construction> from a factory method instead of requiring all that nasty state stuff inside his class.

Instead of learning from many mistakes, author blames entire language.


That certainly been Microsoft's approach to its dev suite. They refused to support C99 and just tell people to use C++ instead. Had to use it for a project and had to sift through code and roll back valid C99 code to make it compatible with their braindead compiler.

C++ has its place and its strong point but it is not a strict superset of C in terms of features and improvement.


Microsoft pretty much let their c compiler rot. It doesn't support dynamic allocation, ie, int arr[numElts];, it doesn't even support var declaration anywhere but at the beginning of a function. Making code I've been writing compatible with msvc has been a pure headache.


It is a C++ compiler, just use C++ and you will be fine.


No. I am using C and specifically some C99 features. They claim C++ is just C and ++ but it isn't.

(There is an Intel compiler that does support C99 btw if anyone is interested but you have to pay for it).

http://software.intel.com/en-us/articles/c99-support-in-inte...


> They claim C++ is just C and ++ but it isn't.

Who says this?

Citing Andrew Koenig 's 1989 statement: "As Close as Possible to C, but no Closer".

Thankfully I would add, as C++ has more strong typing than C.

As for doing pure C99 development on Windows, there are other options as you point out.

Personally as a software developer I don't have issues to pay for the work of others.


Microsoft's mentioned that in their forums in replies to people complaining about lack of C99 -- "just use C++".

> Thankfully I would add, as C++ has more strong typing than C.

Doesn't matter. My project was large, already written in C99, was not in C++. I do not like C++, I don't care for learning virtual destructors mixed with mutliple inheritance, templates stl and friend methods. I am not the only one on the project. So just "don't use it" is easy to say for one person project, when multiple people work on it everyone start to use their favorite subset from C++ huge specification and I don't like that.

> As for doing pure C99 development on Windows, there are other options as you point out.

It was annoying but we just rolled back C99 specific features.


> Microsoft's mentioned that in their forums in replies to people complaining about lack of C99 -- "just use C++".

Yes, this is true. For Microsoft C is a legacy language that can well be replaced by C++ for what they care.

But I think I never saw them stating "... C++ is just C and ++ ...", even of the official communication done by Herb Sutter.

I am on the opposite field, I only touch C if really obliged to do so, which does not happen since 2001.

So I am a bit biased regarding Microsoft's decision, but I do understand some developers would rather stay on C land.


I mostly agree, but there are some exceptions. The principal one is function overloading. This makes code soooo much more readable and easy to use. So, basically, I compile my C code with g++ just for function overloading (which technically makes it not C code, but w/e).


In C11, Type-generic expressions give you something that looks just like overloading (http://www.robertgamble.net/2012/01/c11-generic-selections.h...)

The declarations do not look nice (I don't think that is a matter of taste in this case, compared to overloading in C++), but for the principled who want to use C or for those who have to use C, there is a solution.


just point cpp at your C code and use whatever random collection of C++ features you think make your life easier. We've learned since then that that's a recipe for disaster

How so?


Maybe this makes me an odd C++ user, but in the fifteen years I've been using it, I've never actually used exceptions. I would solve his "how do you handle a failed constructor" problem by either:

1. Try to define classes where construction can't fail and the class is always in a valid state. This works most of the time. Failing constructors are pretty rare in my experience.

2. If it can fail, limit it to classes that can only be constructed on the heap. Encapsulate the failure code in a static method that wraps the constructor which can itself never fail. In other words:

    class Foo {
    public:
      // Creates a new Foo or returns NULL on failure.
      Foo* create() {
        Bar* bar = doThingWhichMayFailAndReturnNull();
        if (!bar) return NULL;

        return new Foo(bar);
      }

    private:
      Foo(Bar* bar) : bar(bar) {} 
      Bar* bar;
    };
This way it is impossible to get a Foo that's in an invalid state, but no exception-handling is required. The caller does have to check for NULL, of course.

3. If I do want to have a class that can be stack-allocated (usually so I can use RAII) and can possibly fail, define an explicit invalid state for the class and check that. Like:

    class Connection {
    public:
      Connection() {
        connected = openConnection();
      }

      ~Connection() {
        if (connected) closeConnection();
      }

      // Outside code is responsible for checking this.
      bool isConnected() const { return connected; }

    private:
      bool connected; // True if in valid state.
    };
I think I generally have the same philosophy as the author. I don't like exceptions (in C++, I love them in other languages), and I really don't like broken-state objects. But that doesn't seem insurmountable to me.


When I worked at Yahoo, the policy was to not use C++ exceptions. We used integer return codes. We mostly followed your #1, in which constructors do no actual work.

The Google coding guide, at least a few years ago, prohibited throwing C++ exceptions.

So you are hardly alone in avoiding them.


If you are using significant libraries, you may find that your library calls have the potential to raise exceptions.


Lets start with the problem: he doesn't want to use exceptions (fine, I can agree with that), but also wants a "constructor" to return an error value if necessary. So whats wrong with the obvious solution?

Create a private constructor with a friend function: a "static" factory method. Bam, no more "semi-initialized" crap that you have to deal with.


To extend that, I don't understand his issue with discarding destructor errors -- most errors I can think of during resource release are mundane (e.g., close(2) returning ECONNRESET) and can safely be ignored. Why not just have `int term();`, which returns the mundane errors, be optional and automatically called from the destructor?

Are there any non-mundane error states that would occur during a stack unwind that you can't safely ignore?


Yes actually: proper destruction and cleanup of a mutex must happen, or else a deadlock will likely occur soon.

But proper cleanup of non-mundane issues is a well known problem in all programming languages, C included. If you don't handle the mutex correctly 100% of the time, issues come abound. At least in C++, there is a methodology / philosophy (RAII) that handles most situations.


Depends on your object. Hopefully, no. To build on your example with files, you can get I/O errors (disk full, disk has a hole in it, etc.) while flushing data, if your file is buffered. (Which is the common case.)


How would you return the error from the private constructor to the static factory method?


Through any means necessary. Exceptions, partially initialized objects, whatever.

But since it is entirely contained within the class now, external code doesn't have to deal with the ugly crap that is inside of the class. IE: A perfect separation of concerns. Adding new exceptions to the constructor isn't a problem, because it is a private constructor... only "friends" can access it. There are a _very limited_ number of functions that can use the constructor.


That's awesome, thanks for the tip!


If you find this tip useful, you probably need to read the Gang of Four's Design Pattern book. Not every design pattern has been useful (omfg, decorators, visitor, and double-dispatch are messy as all hell. Singleton IMO is an anti-pattern, no better than a global variable... etc. etc.).

But a lot of the simpler patterns, like static factory methods, are very useful in day-to-day programming.


I use the visitor pattern in interpreters, parsers, and compilers all the time. Outside of the language domain, though, I've almost never reached for it.

Decorator is a cool idea, but I haven't found much use for it in practice.


Clearly the author spends no time at HN. If he'd have programmed ZeroMQ in Go then all his exception handling would have been handled concisely and beautifully, just as desired. Better though, if he'd programmed it in Haskell there simply wouldn't be any exceptions because there couldn't be any errors. [Shout-out to Mindy Hiller. You go girl!]


Go's 1.0 release didn't happen until 2012. He started working on ZeroMQ in 2007.

Not sure what "spends no time at HN" means. Martin is an unusually excellent software engineer by almost any measure, as evidenced by his years of impactful open-source development - 0MQ being just one example.

And Haskell wouldn't make sense for a project like this, only because of some of his goals with the project - which include: to eventually have it integrated in the Linux kernel; to make it easily usable by code in any language; to have it easily and universally cross-platform; etc.

(Dang... did I just feed a troll?)


I wasn't sure until "Mindy Hiller".


yep


In addition to what was said, ZeroMQ can be used from lots of language through bindings. I haven't seen yet way to use Go written code as library (shared or static) to another language/runtime.

"C" gets you there, if you keep clean "C" interface, and don't let exceptions escape if you have been somehow using C++ internally.

I've had no problem using zeromq directly through the luajit ffi bindings for example.


Or it would have been ZeroMQ.js


Avoiding exceptions I can totally understand—that guideline is broadly understood, and right there in the Google C++ style guide.

I'm struggling to understand the business about constructors, though. Shouldn't a constructor be mostly just providing a sane initial state? What's it doing which has the possibility of needing to throw an exception? I think there's a reasonable middle-ground here, where you put the no-fail stuff in the language-provided constructor, and then have separate initialization functions (with return codes) which take care of the more brittle bits as necessary.

What's an example of an object which requires complex initialization? (I'm assuming that anything IO-related, like a socket, will for testing reasons have been passed in to the constructor rather than created by it.)


C++ is only as horrible as you want it to be. I am currently using C++ internally to write a library and have chosen very carefully which subset to use. It's fun.

- external API is C only

- no exceptions

- some macro magic to allow customization of allocators

- a proxy allocator for the STL containers to use my custom allocators.

- only trivial constructors

It's a pleasure to work with. True, the allocators in C++ suck, but things could be worse and I can still use most of the STL without much troubles.


Based on your allocator woes, it sounds like you might like bsl, with polymorphic allocators: https://github.com/bloomberg/bsl/wiki#the-bde-standard-libra...


He is comparing checking the error status in C to notifying about an error in C++. Think about it. Those are different things. You can think about throwing exceptions as returning an error status, except the code that handles the error in the caller function is isolated into the catch block.


Unless there's no catch block in the caller function. Then it starts to get messy.


To be fair, things may also start to get messy if you forget to check the return value of a function.


Ack. Bur that's something you are not supposed to do in C. Not handling an exception within the function, on the other hand, is perfectly normal, if not encouraged, in C++.


I wouldn't say that it's encouraged, but it indeed is normal. However, one should only do it in the case when the caller function actually can't do anything to handle the error. In this case, the idea is to pass the exception further up to the caller's caller, etc. The bad part is, you have to be mindful of it when writing code, and add exception handling code in callers as necessary, however, I would argue that writing error-handling without exceptions also requires the same level of attention.

I'm not saying exceptions don't have their gotchas ([1]), however, with all due respect, I don't think that your article does a good job at convincing the reader that using exceptions is a poor choice.

[1] http://ptgmedia.pearsoncmg.com/images/020163371x/supplements...


That's because exceptions aren't supposed to be 'handled'. Usually you only catch an exception while you do some cleanup, maybe print some information from it, assuming you even know what you caught, then rethrow.




What makes relevant today is that "go" is steadily becoming a replacement for "C++"


Except that go is a garbage collected language and as such will never be a replacement for C++. I could however see C++ dying, leaving C and go behind.


If rust could do that (someone hacked and remove GC), I'm sure go authors will figure something out. Well, guess it all depends on interest on that topic.


What, as the language everyone loves to complain about? Not yet. That's a year or two away, at least.


Well some performance issues for now https://groups.google.com/d/msg/golang-nuts/8_8gAJCpBsI/pBin... but i believe it would improve over time


Not until it provides some sort of generic development besides casts everywhere.


Better title: "Why I should have written ZeroMQ in C++, not C++ without exceptions."


The entire first half of the article is dedicated to why "C++ with exceptions" is not acceptable for software that strives to have zero undefined behavior. Your proposed "better title" doesn't make sense given the article content.


He doesn't really make a case for not using exceptions though. He misuses the term "undefined behaviour", doesn't discuss RAII and doesn't give any concrete examples.


Writing C++ properly requires an entirely different mind-set to writing in "fancy C" using a C++ compiler. Bitching about exceptions is a sign you don't know what you're talking about, or your expectations are culturally incompatible with the language you're using. When you call a method, you must be aware of what exceptions it could throw and handle those you're interested in from. Ignore this at your own peril.

Return codes in C are easily ignored even if they're serious. In C++ you are compelled to deal with error conditions even if your action is to ignore them. This is a fundamental philosophical difference.

Better title: "Why I should've learned C++ thoroughly before writing ZeroMQ".


What about the author's complaints about exponentially increasing code complexity when introducing new features or new exception types? I feel like the author made an attempt to write c++ the "correct" way and found it unsuitable for the software's goals (zero undefined behavior).


There's been several extended discussions on this, some involving the author, and in virtually every case he's been berating the language for doing things a particular way but often because it's just one way of many and he's ignorant of the other ways of doing it.

There was some bitching about allocators, for example, as if he didn't know about the C++ allocator override feature which isn't even hard to implement. Then there was more confusion about template objects for things like `vector` where he was using them as you might a C linked list library, then complaining that you had to allocate twice as many objects.

I think the author is dimly aware of what C++ really is, and just refuses to play along because they'd rather be writing C code anyway.

John Carmack could probably tear apart every single one of those complaints in ten minutes and have time left over to talk about his new CTO position. That's because Carmack spends the time to learn his tools inside and out and doesn't simply bitch about things being not to his liking.


My way or the highway? In C++? what was that about cutural incompatibility again? ;)

C++ is a broad church, and there is room for all sorts!


There's several "design patterns" in C++ that are used, but you need to conform to at least one of them to have any hope of success.

For instance, do you prefer cheap copies? References? Reference counted pointers with auto-destructors? There's many ways of doing it, you're not necessarily forced to pick any particular one, but not making use of these facilities and instead doing it C-style is making your life harder than it should be.

Between the Standard Library and Boost, there's a lot to pick from. There's no reason to go and roll your own vector just because you don't like or understand the way the standard one works.


Why are so many people wary of third party libraries? I can understand some random, unmaintained gem on RubyGems but in this case we're talking GLib, right?

I guess I mean, shouldn't the stigma be attached to unmaintained or immature libraries not simply third party ones?


GLib is difficult to get to compile on Windows. On https://developer.gnome.org/glib/stable/glib-building.html there isn't even a guide for this.


Based on this critique, the author would probably have found Go even more useful than C. He would not only have avoided the OO and exceptions mess he found problematic in C++, but also the tedious manual memory management in C/C++.


The Author admitted that .... He Said "Practically an advertisement for Go" https://groups.google.com/d/msg/golang-nuts/QWy3YJvcUk0/E22P...


Can Go generate .so libraries that can be called from (and call back to) C/C++ code?


Not sure but there seems to be shared library support for 6l/5l https://code.google.com/p/go/source/detail?r=1eadf11dd1b7b19...


> the tedious manual memory management in C/C++.

The two aren't remotely the same - I find it fairly tedious (but explicit, which is sometimes nice) in C, but reasonably painless in C++ with unique_ptr and similar things.

As has been mentioned in other comments on this article, treating the two as the same language, or versions of it, is a mistake.


Memory management in C++ is not tedious at all (with implicit reference counting).


That is correct, but libzmq uses malloc/free (despite C++).


Yes indeed. I always recommend doing the opposite in C++ projects: use reference counting and not use exceptions.


For those interested, the author is working on a new library along the lines of ZeroMQ with a ZeroMQ compatibility layer called nanomsg[0].

[0]: http://nanomsg.org


Actually, the ZeroMQ compatibility layer was recently cut from nanomsg:

http://www.freelists.org/post/nanomsg/0MQ-compatibility-libr...


Don't fight the language (and this goes for any language). If you fight the language (go against the idioms of the language), you're going to end up unhappy with it. In my opinion, he's fighting the language. Additionally, while he rants at C++ for some of its behavior, he doesn't really address what he'd do in C. Also, he seems to not understand what "undefined behavior" is. Last, RAII is your friend in C++, and IMHO, is the difference between programming in "C with some extra stuff, but mostly more verbose" and C++.

Exceptions:

> The decoupling between raising of the exception and handling it, that makes avoiding failures so easy in C++, makes it virtually impossible to guarantee that the program never runs info undefined behaviour.

The example is basically that when you call a function, it could raise any error, so we don't know which to catch. This problem exists in most languages where errors can occur (I've had to deal with this a lot in Python). So, C. How do you translate the errors across function calls? are you, or are your functions just returning 0/1? (at which point, you've lost much of the richness of exceptions, I think) Instead of handling errors now at the source and handler, you'll also need to handle them at every intermediate point (if (f()) { free things; return ???; }), and this burden makes exceptions wonderful.

> Consider what happens when initialisation of an object can fail. Constructors have no return values, so failure can be reported only by throwing an exception.

Raise an exception, except… (no pun intended)

> However, I've decided not to use exceptions.

(You're fighting the language.) Is this just a complaint that maintaining exception safety in ctors is difficult? unique_ptr and shared_ptr (and RAII) make it fairly straight-forward and easy, and often a ctor can be completely exception safe without try/catch. (If releasing the partially constructed state is the issue, it's hard to tell.)

> if termination can fail, you need two separate functions to handle it

Yes, you will. Honestly, this never sat well with me, but I think it makes good sense. But his example seems to imply you can fold them into one function in C, which you simply can't (under the same assumptions that the C++ code was held to). If "termination" can fail, you'll still need thing_terminate() and thing_deallocate(), or you'll never be able to deallocate the thing; or you're assuming that the C thing_terminate() will just report the error and deallocate anyways. The larger C++ problem remains in C, and the author doesn't touch on it: if you're (manually, in C) unwinding the stack due to error, and you need to release a thing in the process, and that release fails: what do you do? C++ gives you two options, in my opinion: either you ignore it/log it, by not throwing in a dtor, or (and people don't like this one) you throw in the dtor and risk there being two exceptions. C++'s response will be to terminate the program. In C — or any language — what would you do with two errors?


> Additionally, while he rants at C++ for some of its behavior, he doesn't really address what he'd do in C.

He already started working on "C" re-implementation of ZeroMQ with different API and zeromq wrapper on top of it (so it could be used as zeromq replacement).

https://github.com/250bpm/nanomsg


> So, C. How do you translate the errors across function calls? are you, or are your functions just returning 0/1? (at which point, you've lost much of the richness of exceptions, I think)

If you think your options are only "returning 0/1", you're not being creative enough.

For example COM has a 32-bit integer called HRESULT that all methods return. The high bit says something is an error. A few bits in the high 16 bits indicate where an error is from. The low 16-bits are private to whatever space is specified in the aforementioned bits. So if you take an error like 0x80070002 you can say it's an error, from the Win32 subsystem, and the error code is 0002 or file not found in this specific instance. Another component can set the high bits differently and end up with their own 16-bit error codes. I find when I write COM code the errors are much more meaningful than in a high-level language because it's all very explicit, you're constantly thinking about it at every function call, whereas in exception-based systems it's all just very haphazard and hand-wavy.

Or you could look at something like glib. Each method might have an out parameter which is a gerror object. This includes an error code and a string.

In other words there are several conventions out there. If you pick one and stick with it across a code base you can be very productive with a minimal amount of nonproductive "philosophical questions" like the ones in your post. Even if you have several libraries which have different conventions, you can adapt their conventions to the conventions of your code base and it's typically not a big deal.

> Instead of handling errors now at the source and handler, you'll also need to handle them at every intermediate point (if (f()) { free things; return ???; }),

Actually this "early return" pattern is quite bad in plain C, because if you have N allocations for example, each "return" statement needs to free between 0 and N buffers before leaving the function. That gets very maddening. One way to avoid this is to have only one "return" statement at the end of your function and free any loose buffers if-and-only-if they are non-null. This involves either constantly re-checking some error status, or using a goto to jump to the end of your function when something fails. IMO the 2nd is much cleaner. (Let that wrap around your high-level-language brain... but also know that any large C only code base is doing the same thing if they are sane; grep the Linux kernel for "goto" and you might start to get a picture... and while you're bashing C error handling and probably thinking ill for me for pulling out "goto", please keep in mind your "exception" concept is really just a goto that crosses stack frames...) In the end this pretty well simulates what you'd do in destructors in a C++ code base following the RAII pattern.

> and this burden makes exceptions wonderful.

In my experience it, on the contrary the exception concept just makes sure you have no idea that stuff can fail, and every small failure in a "shockingly" unexpected place blows up your entire program and terminates the process. Or you have Java's goofy "checked exceptions" concept and so lazy programmers will introduce do-nothing catch blocks to shut up the compiler.


>For example COM has a 32-bit integer called HRESULT that all methods return. The high bit says something is an error. A few bits in the high 16 bits indicate where an error is from. The low 16-bits are private to whatever space is specified in the aforementioned bits. So if you take an error like 0x80070002 you can say it's an error, from the Win32 subsystem, and the error code is 0002 or file not found in this specific instance. Another component can set the high bits differently and end up with their own 16-bit error codes. I find when I write COM code the errors are much more meaningful than in a high-level language because it's all very explicit, you're constantly thinking about it at every function call, whereas in exception-based systems it's all just very haphazard and hand-wavy.

If you want to have an exception that just contains an error code you can do that (e.g. java's SQLException works that way). Personally I find it much more useful to be able to give errors textual name, and declare a hierarchy relationship between errors.

> Actually this "early return" pattern is quite bad in plain C, because if you have N allocations for example, each "return" statement needs to free between 0 and N buffers before leaving the function. That gets very maddening. One way to avoid this is to have only one "return" statement at the end of your function and free any loose buffers if-and-only-if they are non-null. This involves either constantly re-checking some error status, or using a goto to jump to the end of your function when something fails. IMO the 2nd is much cleaner. (Let that wrap around your high-level-language brain... but also know that any large C only code base is doing the same thing if they are sane; grep the Linux kernel for "goto" and you might start to get a picture... and while you're bashing C error handling and probably thinking ill for me for pulling out "goto", please keep in mind your "exception" concept is really just a goto that crosses stack frames...) In the end this pretty well simulates what you'd do in destructors in a C++ code base following the RAII pattern.

Sure - exceptions exist to replace something you would otherwise have to use goto to do, that's pretty much the entire point. But they're better than goto for the exact same reason that a while loop is better than implementing a loop with goto.

> In my experience it, on the contrary the exception concept just makes sure you have no idea that stuff can fail, and every small failure in a "shockingly" unexpected place blows up your entire program and terminates the process. Or you have Java's goofy "checked exceptions" concept and so lazy programmers will introduce do-nothing catch blocks to shut up the compiler.

You have the exact same problem in C. Either programmers have no idea that library functions can fail and never check return codes, or you have something like gcc's warn_unused_result and lazy programmers introduce do-nothing error-code checks.


> You have the exact same problem in C. Either programmers have no idea that library functions can fail and never check return codes, or you have something like gcc's warn_unused_result and lazy programmers introduce do-nothing error-code checks

You forgot the other option. The careful programmer knows they have to check return codes and is suspicious when it's not happening. With exceptions you have no idea when they can happen because it's not part of the function signature. Or in the case of Java, handling errors is like homework, a constant struggle to keep the nagging compiler happy, instead of a consistent style that you apply throughout a code base.


I don't understand the error handling example. Can't you just do the same thing in C++?


Exactly. The author thought that using C++ means you are forced to use exception handling, despite the fact that most C++ engineers don't use it. And he had this dumb idea about doing non-trivial things in constructors, another thing most experienced C++ engineers don't do. I don't care what experience he claims to have; he is a noob.


[2012]




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: