10 ms·
Why I should have written ZeroMQ in C, not C++ (2012)
- dragontamer 13y agoLets 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.
- rumcajz 13y agoHow would you return the error from the private constructor to the static factory method?
- dragontamer 13y agoThrough 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.
- lcampbell 13y agoTo 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?
- dragontamer 13y agoYes 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.
- deathanatos 13y agoDepends 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.)
- pdmccormick 13y agoThat's awesome, thanks for the tip!
- dragontamer 13y agoIf 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.
- munificent 13y agoI 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.
- X-Istence 13y ago[2012]
- mmariani 13y agoPrevious discussion: https://news.ycombinator.com/item?id=3953434 https://news.ycombinator.com/item?id=3953434
- scott_s 13y agoAnd the followup: https://news.ycombinator.com/item?id=4455225 https://news.ycombinator.com/item?id=4455225
- babawere 13y agoWhat makes relevant today is that "go" is steadily becoming a replacement for "C++"
- twic 13y agoWhat, as the language everyone loves to complain about? Not yet. That's a year or two away, at least.
- babawere 13y agoWell some performance issues for now https://groups.google.com/d/msg/golang-nuts/8_8gAJCpBsI/pBinmMdsdbwJ https://groups.google.com/d/msg/golang-nuts/8_8gAJCpBsI/pBin... but i believe it would improve over time
- pjmlp 13y agoNot until it provides some sort of generic development besides casts everywhere.
- w3pm 13y agoExcept 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.
- popee 13y ago
- npalli 13y agoPrevious discussion with 207 comments https://news.ycombinator.com/item?id=3953434 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 https://news.ycombinator.com/item?id=4455225 I wonder if anything new can be said here.
- babawere 13y agoMost importantly can go lang really take the place of C++
- wglb 13y agoYes, please.
- jerf 13y agoIn 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".
- jandrewrogers 13y agoNo, 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.
- masklinn 13y agoAnd 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.
- tptacek 13y agoLibrary 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.
- 10098 13y agoActually, 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.
- w3pm 13y agoFor 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.
- masklinn 13y ago> I wish C had support for templates Generics. Polymorphic functions and collections don't require the complexity of C++ templates.
- beagle3 13y ago
- bcoates 13y agoThe 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.
- rdtsc 13y agoThat 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.
- aa0 13y agoMicrosoft 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.
- pjmlp 13y agoIt is a C++ compiler, just use C++ and you will be fine.
- rdtsc 13y agoNo. 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-intel-c-compiler http://software.intel.com/en-us/articles/c99-support-in-inte...
- lazyjones 13y agoBased 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++.
- babawere 13y agoThe Author admitted that .... He Said "Practically an advertisement for Go" https://groups.google.com/d/msg/golang-nuts/QWy3YJvcUk0/E22PU93EdlMJ https://groups.google.com/d/msg/golang-nuts/QWy3YJvcUk0/E22P...
- cpeterso 13y agoCan Go generate .so libraries that can be called from (and call back to) C/C++ code?
- babawere 13y agoNot sure but there seems to be shared library support for 6l/5l https://code.google.com/p/go/source/detail?r=1eadf11dd1b7b19d4857681363553c2cfd2ad47d https://code.google.com/p/go/source/detail?r=1eadf11dd1b7b19...
- killedbydeath 13y agoMemory management in C++ is not tedious at all (with implicit reference counting).
- lazyjones 13y agoThat is correct, but libzmq uses malloc/free (despite C++).
- killedbydeath 13y agoYes indeed. I always recommend doing the opposite in C++ projects: use reference counting and not use exceptions.
- 10098 13y agoHe 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.
- rumcajz 13y agoUnless there's no catch block in the caller function. Then it starts to get messy.
- 10098 13y agoTo be fair, things may also start to get messy if you forget to check the return value of a function.
- rumcajz 13y agoAck. 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++.
- 10098 13y agoI 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/exception_handling_article.html http://ptgmedia.pearsoncmg.com/images/020163371x/supplements...
- 13y ago
- commentzorro 13y agoClearly 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!]
- redsymbol 13y agoGo'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?)
- delluminatus 13y agoyep
- detrino 13y agoI wasn't sure until "Mindy Hiller".
- malkia 13y agoIn 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.
- stusmall 13y agoOr it would have been ZeroMQ.js
- Kiro 13y agoI don't understand the error handling example. Can't you just do the same thing in C++?
- TwoBit 13y agoExactly. 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.
- voyou 13y agoBetter title: "Why I should have written ZeroMQ in C++, not C++ without exceptions."
- astrodust 13y agoWriting 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".
- w3pm 13y agoWhat 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).
- astrodust 13y agoThere'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.
- deathanatos 13y agoDon'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?
- mikepurvis 13y agoAvoiding 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.)
- jbrechtel 13y agoWhy 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?
- wolfgke 13y agoGLib is difficult to get to compile on Windows. On https://developer.gnome.org/glib/stable/glib-building.html https://developer.gnome.org/glib/stable/glib-building.html there isn't even a guide for this.
- the_mitsuhiko 13y agoC++ 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.
- apaprocki 13y agoBased on your allocator woes, it sounds like you might like bsl, with polymorphic allocators: https://github.com/bloomberg/bsl/wiki#the-bde-standard-library https://github.com/bloomberg/bsl/wiki#the-bde-standard-libra...
- munificent 13y agoMaybe 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.
- joe_the_user 13y agoIf you are using significant libraries, you may find that your library calls have the potential to raise exceptions.
- asher 13y agoWhen 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.
- themckman 13y agoFor 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 http://nanomsg.org
- erickt 13y agoActually, the ZeroMQ compatibility layer was recently cut from nanomsg: http://www.freelists.org/post/nanomsg/0MQ-compatibility-library-removed http://www.freelists.org/post/nanomsg/0MQ-compatibility-libr...