25 ms·
Why should I have written ZeroMQ in C, not C++ (2012)
- Todd 11y agoEarlier 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 11y agoAnd 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
- jsolson 11y agoThis 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.html http://www.boost.org/doc/libs/1_55_0/doc/html/intrusive/list...
- rando3826 11y agoAnd 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++.
- a3n 11y agoWhat a strange language, that so much sage wisdom involves what parts of it you shouldn't use. (Former C++ programmer, a long time ago, before the "don't use these parts" advice became a thing.)
- derefr 11y agoThere was a post here recently, explaining type systems are effectively about restricting you from encoding nonsensical constructions. C is ASM with the ability to encode some nonsensical ASM instruction-sequences removed, etc. The weird thing about C++—and the reason it didn't just absorb/replace C—is that it gives you the ability to encode strictly more nonsensical things than C does. In that sense, C looks more like the descendant of C++ than the other way around!
- pjmlp 11y ago> The weird thing about C++—and the reason it didn't just absorb/replace C—is that it gives you the ability to encode strictly more nonsensical things than C does. In that sense, C looks more like the descendant of C++ than the other way around! Yet the most famous current C compilers are all written in C++. If it wasn't the rise of open source and its community attachment to C, mainly in GNU and BSD circles, C would have been long replaced except for the embedded space.
- gridspy 11y agoYes, it shares that 'feature' in common with JavaScript.
- bajsejohannes 11y agoNo, actually, it doesn't. C++ is a huge language, javascript is a relativly small one. I know it's funny to compare the size of "Javascript the good parts" with "Javascript the definitive guide", but that's can be attributed to writing style more than percentage of language covered.
- MaulingMonkey 11y ago> No, actually, it doesn't. Perhaps less in what features to avoid, but there's plenty of hidden gotchas to avoid in JavaScript still. https://www.youtube.com/watch?v=et8xNAc2ic8 https://www.youtube.com/watch?v=et8xNAc2ic8 https://dorey.github.io/JavaScript-Equality-Table/ https://dorey.github.io/JavaScript-Equality-Table/ > C++ is a huge language, javascript is a relativly small one. C++'s ISO/IEC 14882:2003 weighs in at 786 pages, compared to ECMA-262 (5.1 Edition)'s 258 pages (measuring by PDF reader). A ~3x difference, but not quite the order of magnitude I'd expect from huge vs small.
- dragontamer 11y agoGood luck finding a language out there that doesn't have pitfalls.
- moonchrome 11y agoTo be fair I think most languages don't have pitfalls as defaults in the C++ sense :D
- Daishiman 11y agoThe amount of things in C++ to avoid practically constitute a language by itself.
- moonchrome 11y agoAnother amusing part is that it's a moving target - some parts of the language actually got better with implementations and spec improvements - so there's a large number of people avoiding useful features on outdated practices (they learned "don't use X" without the reasoning behind it :) )
- dragontamer 11y agoBut most languages that have seen prominent use have obscure corners that no one should ever visit. PHP has absolutely awful parts for example. (Magic Quotes anyone?) Even relatively "clean" languages like Java or C# can be completely mucked around if you abuse reflection or do dumb things (ie: have property getters / setters in C# do something insane... like get {return blah++;} ) I mean, any language that has mutexes (ie: all of them) can more or less lead to absolutely dangerous situations. C++ RAII may not be the best (compared to D or Rust or whatever), but C++ is the most widespread language that has complete RAII support. If you even learn the most basic of C++ patterns: shared_ptr<>, RAII destructors... (which are useful concepts in C++'s "replacements" anyway), C++ becomes not only a manageable language... but one of the most widespread useful languages current right now.
- vitaut 11y agoActually 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.
- nulltype 11y agoThe pitfalls of C++ that I've seen are mostly the things people build with the safe alternatives.
- vitaut 11y agoCould you give an example?
- nulltype 11y agoI've seen some pretty impossible to comprehend or debug uses of templates. Perhaps my favorite, which represents most of the pitfalls you can create with C++ is the Boost library. And of that, the shining star is this page: http://www.boost.org/doc/libs/1_57_0/libs/geometry/doc/html/geometry/design.html http://www.boost.org/doc/libs/1_57_0/libs/geometry/doc/html/...
- vitaut 11y agoI wouldn't call it a pitfall. Boost is written by C++ experts who like to push the boundaries of what can be done with the language. This doesn't mean that you have to debug their code or overuse templates yourself and there is nothing in the language that encourage this. For common cases such as in the standard library, templates are pretty easy to use and trivial to debug with modern compilers.
- GFK_of_xmaspast 11y agoGiven the design constraints: """ A generic library should be able to calculate the distance: for any point class or struct, not on just this mypoint type in more than two dimensions for other coordinate systems, e.g. over the earth or on a sphere between a point and a line or between other geometry combinations in higher precision than double avoiding the square root: often we don't want to do that because it is a relatively expensive function, and for comparing distances it is not necessary """ I suspect a little bit of complexity is necessary. (And can you even build something like this in a more fashionable language?)
- Shorel 11y agoC with classes is undervalued, and templates are overvalued, IMO. Just switching compilers makes the C code more robust.
- eropple 11y agoTemplates can be abused, but there's real value to simple ones such as container classes and even shared_ptr and unique_ptr. The subset of C++ I personally use is pretty small, but I hope I never go back to manual memory babysitting.
- waps 11y agoTemplates are the main thing in C++ that you can easily use everywhere with zero costs in compatibility. And they certainly beat C's "equivalent", macros.
- Retra 11y agoYou suggestions are great if you don't want to use anything like the standard library or just about any other library. Unless you want to write tons of boiler plate wrapping these things up.
- clumsysmurf 11y agoSometime in the future: "Why I should have written nanomsg in Rust, not C".
- cesarb 11y agoIf 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.)
- kinghajj 11y agoDo you mean "unsafe" instead of "undefined"? Here's what I found after a Google search, by one of the core Rust devs (unsurprisingly). https://github.com/pcwalton/multilist https://github.com/pcwalton/multilist
- dschatz 11y agoI 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.
- rkwasny 11y agoThis 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 11y ago>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 11y agoMultiple return values is now handled in C++ with tuples and std::tie.
- cjslep 11y agoWhile 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.
- reubenmorais 11y ago> "which STL did this shared object link against" This alone makes exporting STL objects pretty much impossible in a sizable cross-platform project, in my experience.
- nickpsecurity 11y agoInteresting 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. :)
- voidlogic 11y ago>I actually see an opportunity for a C-compatible or C-competitive systems language here to beat C++ at its own game. Go or Rust? They might not quite match your dream outlined above, but I think in practice they are pretty much used with the purpose of achieving something similar. They both can be "unsafe" as needed and integrate well with C and ASM.
- marktangotango 11y agoVala gets no love in these discussions; a C# subset that compiles to C and gobject.
- gue5t 11y agoVala is not a C# subset (though it does significantly resemble C#) and really isn't intended or suitable for writing high-performance code or reusable low-level libraries like C or C++ are; it's a way to write GObject-style C without as much boilerplate as it takes in C. For that niche it's quite nice, and helps avoid refcounting errors which can be very subtle in C, but Vala is still a fairly leaky abstraction over the C to which it compiles.
- deleted 11y ago[deleted]
- derf_ 11y agoThese 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 11y agoAnd even so, a lot of very senior C++ users will disagree with the FQA's recommendations.
- MaulingMonkey 11y agoEven 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.
- bkjelden 11y agoI was really struggling to pick up c++, and when I read the FQA I had a 'it's not me, it's you' moment. I don't detest the language the way he does, but the FQA really resonated with me. There's no one feature that kills the language, but it seems like everywhere you go you run into a feature that feels like it was designed to trip you up, and it gets tiring after a while.
- alexnewman 11y agoHe means rust
- talles 11y agoException 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 11y agoWat? I thought callbacks are the goto of 21st century.
- santaclaus 11y agoI thought plain threads are the goto of the 21st century?
- Mikhail_Edoshin 11y agoExceptions are just a way for a piece of code to say to its callers: "Sorry guys, this is over my head, I give up, sort this one yourselves." And since most pieces are pretty simple, they encounter these situations routinely. Now, if you want things like nice syntax you positively need exceptions at least as a concept. Otherwise you cannot even add two numbers: take `a + b`, what if the result overflows? What if you write `x = foo(y, bar(z))` and `bar()` fails to deliver? Maybe the try/throw/catch syntax is not that convenient, or maybe the documentation doesn't list all the exceptions that may arise and you feel out of control, or maybe a particular implementation is slow; these are all valid reasons. But I don't see how you can get rid of the idea of exceptions. Well, maybe functional languages have something here, but I'm not an expert in this area; my understanding is that you may define a function as returning either `Error` or `UsefulResult` so you can branch accordingly.
- vitaut 11y agoFrom 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 11y agoI 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.
- kabdib 11y agoI 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 11y agoThe 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 11y agoI'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++.
- KayEss 11y agoThis 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 11y agoI 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.
- TwoBit 11y agoAs 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 11y agoHow does a proper C++ constructor report errors?
- maddening 11y agoSeveral 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.html#Doing_Work_in_Constructors https://google-styleguide.googlecode.com/svn/trunk/cppguide....
- juliangregorian 11y agoThis seems like pretty much what the author does, no? A gimped constructor with the real work in an initializer?
- helmut_hed 11y agoCompiling 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 11y ago> 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.
- yason 11y agoThe 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 11y ago> "what I see is what I have" I guess you have not yet seen too much UB or compiler specific semantics then.
- zvrba 11y agoI'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/doc/html/boost_numericconversion/definitions.html http://www.boost.org/doc/libs/1_55_0/libs/numeric/conversion...
- pjmlp 11y agoMostly inherited from C, actually.
- zvrba 11y agoWell, "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.
- helmut_hed 11y agoI'm curious about that last bit. I occasionally hear this argument that size_t being unsigned is a bad thing. Presumably the standard library designers would claim that a size cannot be negative, so this is appropriate. Why, in your view, should that be a signed quantity?
- zvrba 11y agoUnsigned numbers in C++ are completely unsuitable for restricting the domain range because their "rank" (technical term) is higher than that of equally-sized signed types, which means that all operands are promoted to unsigned if one operand is unsigned. Of course, the result is unsigned too. Now, you often want to do arithmetic with numbers, right? You could think that, given two containers with c1.size() < c2.size(), that c1.size()-1 < c2.size()-1, right? Wrong. Because of default promotion rules and size() returning an unsigned number, (signed) 1 is converted to an unsigned number and the whole expression is evaluated in modulo arithmetic. Now, if c1 is empty (c1.size() == 0), then subtracting 1 from 0 in unsigned arithmetic wraps around (this is how unsigned arithmetic is defined) to a huge number (4 billion on 32 bit machines). The simple comparison above will fail in THE singular case of having an empty container on left hand side. You also lose any possibility of detecting errors. (E.g., if size() were signed and could return a negative number, you could effectively assert on that. If assert triggered, then something really bad happened -- maybe two concurrent threads modifying the data structure w/o proper synchronization.) I personally think it makes reasoning about a program harder; the above comparison was just the most trivial example. unsigned<>signed comparisons also trigger a bunch of warnings, because I use signed numbers for everything else, so the code ends up having a bunch of unnecessary casts. (I do not want to disable the warning because genuine mishaps of mixing signed/unsigned do happen.) I believe the consensus is (also Stroustrup's recommendation) is that unsigned should be used as "give me modulo arithmetic" instead of "this number cannot be negative". So, IMHO, unsigned size() was a mistake and is a big PITA when you try to develop robust software and creates less maintainable code (e.g. in the above simple example, you'd have to special-case the c1.empty() case with if). Casting size() to signed type immediately if I'm going to do anything else than iterate over the container is the least evil for me.
- rumcajz 11y agoAuthor 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
- cpp098 11y agoC++ 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?
- shepardrtc 11y agoI 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.