9 ms·
Why should I have written ZeroMQ in C, not C++ (2012)
- missblit 4y agoC++ gives lots of optional features, so it requires a bit of discipline to use effectively and avoid running into the state explosion that the author ran into for both their gripes. But ironically one the reasons the author chose C++ is that enforced a consistent coding style (As a C++ programmer: I don't believe you). There's just as many pitfalls in C as well like unstructured programming via goto, signal handlers, not being disciplined about structs when doing OOP, etc. --- Take the second issue about partially initialized objects being hard to avoid without using exceptions in C++. You can actually do something similar to the C approach the author prefers! Just make the iffy constructor private, then wrap it in a builder function which can either return the object or an error. However that requires the intuition that requiring the user to call a follow up init() or is_valid() function is a bad idea, which requires a lot of C++ experience.
- cpgxiii 4y agoA classic case of "I don't want to use exceptions, I don't want to use any of the other C++ features that would get me out of this corner, and now I don't like it". Don't want to use exceptions? Keep the constructors private/protected, provide factory functions that return a Result<Type, Condition> (in whatever value/pointer forms you need) and keep all the benefits of RAII and enforced invariants without requiring your users to catch exceptions.
- programmer_dude 4y agoCan you guarantee the code in the C++ stdlib will not throw exceptions? Scratch that, the stdlib API is unusable with exceptions disabled. None of the functions return an error code.
- petmon 4y agoThe C++ stdlib (aka STL) is 100% usable without exceptions. This is deliberate: major players and code bases disable exceptions, including Google, LLVM, Firefox, so it has to work and work well. And disabling exceptions is easy, simply pass `-fno-exceptions` to the compiler. Fallible C++ functions do return an error code. errno is part of C++. https://en.cppreference.com/w/cpp/error/errno https://en.cppreference.com/w/cpp/error/errno
- jcelerier 4y agowouldn't say 100%, and -fno-exceptions does not disable exception propagation, it only prevents you from having the keywords "throw / try / catch" in your code. It's because it's not a C++ feature but a platform one which C++ leverages - when GCC compiles C code for instance it makes sure that their stack can be unwound in case there would be a callback implemented in C++ which would throw an exception (and because this is also a generally useful ability to have). If you use the stdlib it will still throw (but disabling exceptions just means that now you can't catch it and it will be an automatic crash if it reaches main): https://gcc.godbolt.org/z/1s11f99a6 https://gcc.godbolt.org/z/1s11f99a6 as the "throw" themselves are in the stdlib's implementation files, not in the headers so it isn't "your" code and isn't affected by -fno-exceptions.
- programmer_dude 4y agoLOL, you haven't actually tried using C++ without exceptions have you?
- benreesman 4y agoI have a pretty big C++ codebase that uses noexcept on almost everything. There are a few cases where deep in the call graph something has to have a try block, but most of the unavoidable exception semantics are around std::bad_alloc, and you’re in pretty deep at that point. clang-tidy rarely if ever misses on noexcept violations these days. Not sure what the LOL is about.
- programmer_dude 4y ago>And disabling exceptions is easy The LOL was directed at this. You can never disable exceptions in C++. No keyword or flag exists which can do this for you (-fno-exceptions and noexcept notwithstanding). >I have a pretty big C++ codebase that uses noexcept on almost everything. Noexcept is not enough see nearby comment by another user for explanation.
- benreesman 4y ago
- fweimer 4y agoMostly it works, except for memory allocation failures. These will terminate the process. But then, the Itanium C++ ABI has some quirks that make it impossible to throw an exception (any exception) in all out-of-memory conditions, so perhaps that's okay. (Throwing an exception must allocate, and the ABI currently does not allow to throw a pre-canned std::bad_alloc on allocation failure instead.)
- bregma 4y agoNot only does the Itanium C++ ABI allow for using a pre-canned std::bad_alloc (because __cxa_allocate_exception() can do whatever it wants on an allocation failure), but most of the common implementations do just that.
- fweimer 4y agoI meant that the interface for __cxa_allocate_exception is designed in such a way that it cannot substitute std::bad_alloc for the exception being allocated.
- MrQuincle 4y agoHow's that implemented? I'm just used to embedded. Implementing sbrk etc. Some flags i use to disable exceptions # The default flags will be used in all build types # The C/C++ shared flags are in DEFAULT_C_AND_CXX_FLAGS, the ones particular for C or C++ in DEFAULT_X_FLAGS # The flags mean the following: # * no-exceptions disable exceptions support and use C++ libs without exceptions # * delete-dead-exceptions throw away instructions which only purpose is to throw exceptions # * no-unwind-tables stack-walking code for backtrace generation (TODO: we might want to enable this in Debug!) # * no-non-call-exceptions do not convert trapping instructions (SIGSEGV, etc) into exceptions # * thumb use Thumb mode (optimized instruction set) # * function-sections put every function in its own segment, now it can be garbage collected # * data-sections put every variable in its own segment # * no-strict-aliasing allow *int = *float aberrations # * no-builtin do not use abs, strcpy, and other built-in functions # * short-enums use a single byte for an enum if possible SET(DEFAULT_CXX_FLAGS "-std=c++17 -fno-exceptions -fdelete-dead-exceptions -fno-unwind-tables -fno-non-call-exceptions")
- kovac 4y agoThat's a lot of excessive code to write just to avoid using exceptions though.
- adhesive_wombat 4y agoAnd std::optional can really help with the semantics of that Result type.
- mike_hock 4y agoThe strawman argument against constructors is so contrived it's cringeworthy. If he wants to use that init/term pattern, he can just not use constructors/destructors and do it all in init/term (not suggesting that he should). What, you have to add a "state machine" to your objects because other developers on the project will willfully ignore the project's policies? What about the C version? It also has a half-initialized state between the declaration and `foo_init`. Except here you can't use defensive programming against it and accessing the half-initialized state is simply UB because it's in fact fully uninitialized. Or maybe half-initialized or initialized wrong if the developer hand-initializes some fields.
- dleslie 4y agoTo the first point: exceptions have been eschewed, or considered an anti pattern in game development for decades, and for all the reasons given and more. They're hard to reason about and difficult or impossible to make performant. The second is a genuine concern, but overcome with factories. If your class needs additional resources beyond memory allocation then those resources need to be acquired before construction, or its construction must support automatic self destruction. It doesn't matter if it's C or C++ you're using, really; you should acquire before use.
- cgrealy 4y ago`class exception1 {}; class exception2 {}; try { ... if (condition1) throw my_exception1 (); ... if (condition2) throw my_exception2 (); ... } catch (my_exception1 &e) { handle_exception1 (); } catch (my_exception2 &e) { handle_exception2 (); }` He posts this as an example of why C++ sucks. Yeah, but no one actually writes code like that. The whole point of exceptions is for situations where you cannot handle the error locally. If you’re throwing and catching exceptions in the same method, you’re doing it wrong.
- deleted 4y ago[deleted]
- jgavris 4y agoEventually you'll be able to use std::expected in C++23! https://en.cppreference.com/w/cpp/header/expected https://en.cppreference.com/w/cpp/header/expected Don't throw exceptions, require the caller to handle errors and propagate them up the stack (everything returns an expected) if they cannot be handled. You are forced to model the error domains instead of just throwing an exception and assuming the caller knows to catch it and do something with it. Java has checked exceptions, but, Kotlin decided to abandon them. The nice codebases I have worked on stick to the Result<T,E> type in Swift or Kotlin. And thus you are forced to 'translate' errors (exceptions?) as described in Alan Griffith's 'Exceptional Java'. https://accu.org/journals/overload/10/48/griffiths_406/ https://accu.org/journals/overload/10/48/griffiths_406/ "If a checked exception is thrown (to indicate an operation failure) by a method in one package it is not to be propagated by a calling method in a second package. Instead the exception is caught and "translated". Translation converts the exception into: an appropriate return status for the method, a checked exception appropriate to the calling package or an unchecked exception recognised by the system. (Translation to another exception type frequently involves "wrapping".)" If you can't wait for C++23, there's a single header implementation here. https://github.com/TartanLlama/expected https://github.com/TartanLlama/expected
- deleted 4y ago[deleted]
- rich_sasha 4y agoI last used C++ extensively in the 00s but my general impression of modern C++ is that as it becomes more and more complex, it tackles it's complexity... by adding more complexity.
- primeblue 4y ago
- DoctorDabadedoo 4y agoThat's exactly how it is, every few years there is so much added without taking almost nothing back that it feels a different language, with so many styles out there it's challenging, to say the least.
- sweetgiorni 4y agoI'm probably misunderstanding something here, but isn't the author's criticism of exception bubbling and duplicate code easily resolved with exception inheritance? Define a base exception that all others inherit from and catch that in the general case. If you want to take action when a particular exception occurs, add a catch block for it. Duplicate error handlers could be factored out into helpers.
- ncmncm 4y agoThe author complains about his own misunderstanding of, and consequent misuse of exceptions. Put all your cleanup in destructors. All of it. They exercise that code all the time, on every run. Then, throwing always does the right thing, with no extra effort.
- JonChesterfield 4y agoGood thing cleanup code can never fail, what with there being no way to report failure from a destructor.
- benreesman 4y agoC++ exceptions are considered by many a misfeature and that consensus seems to be growing. There are myriad corner cases with accidental slicing, pretty nasty performance failure modes, and toolchain/flag/platform quirks. You can do pretty OK Maybe/Either types in modern C++, but there’s not an obvious way to get the ergonomics you want. At the time this was written the right move was usually to signal failure with a return value and return your successful value with an out-parameter. Java can do exceptions with VM/JIT support and it’s much more predictable, but doesn’t hit large parts of the domain. Anything north of Java in runtime support is targeting non-overlapping domains to C++ in 2022. Rust is doing a somewhat exciting experiment with custom syntax for Maybe/Either. It’s a bit soon to say how it will age. I fear it won’t turn out to be general enough, but it beats the hell out of errno.
- ncmncm 4y agoThis essay has not aged at all well.
- Sirenos 4y agoI'm not sure why this was posted. It's aged terribly and I think we can all agree that sidestepping constructors for init functions is basically C++ without the ++. Not sure how that's any different from writing in C.
- stcont 4y agoWe used init functions at my last job and got to enjoy a lot of the other benefits of C++. Lambdas, templates, std::variant, the STL, the list goes on. Not wanting to use feature X of C++ doesn’t make it a shittier C.
- archi42 4y agoI agree with you, GP is wrong about the "generally". We did use the isValid variant (and the odd factory once in a blue moon) at $former_job, no exceptions but a lot of other very nice things about C++ (as they became available). But the article is a decade old, the project started 2007. So there is a good chance the project couldn't even use c++11 features. I still recall using initializer lists on my Linux machine only to have the nightly Windows builds fail (we were stuck on an old msvc for a while; the day we upgraded it there was much rejoice). //Some light editing
- languageserver 4y ago> We used init functions at my last job has the factory pattern somehow managed to crawl into a hole?
- stcont 4y agoinit functions and factory pattern are not orthogonal.
- languageserver 4y agothat is not the statement. they say C++ sucks because init pattern means an unititialized object might be used. the factory pattern solves that
- DethNinja 4y agoC++ is a swiss army knife, every project only requires a small subset of the provided tools and that is the beauty of the language. If you think exceptions would complicate the code, then there is always the option of not using them and instead utilising function(errCode&) pattern.
- jchw 4y agoThe comments here reflect that since this was written, both the C++ language and programming as a whole have moved forward a decent bit. For example, the existence of move has made the pattern of a private constructor a bit less cumbersome. That said, I think they still raise a good point: making a reliable foundational library in C++ is hard without the privelege of being able to make a lot of assumptions about the runtime environment and compiler flags. The exceptions in dtor thing is still true, so if you can't enforce -fno-exceptions, you're left in an awkward place. There are always more answers, but I think the fact that there's so many questions to begin with illustrates the problem.
- scaredginger 4y agoJust mark all destructors noexcept?
- jchw 4y agoNoexcept doesn't prevent your dtor from throwing, it just means that if it does throw, it will call std::terminate rather than unwind (thus, callers can assume it doesn't throw, since it will abort instead.) For the problem mentioned in the article, this is not a solution.
- usrnm 4y agoDestructors already are noexcept by default
- deleted 4y ago[deleted]
- zasdffaa 4y agoRead this paper before and don't buy it. // c++ if (condition1) throw my_exception1 (); you throw an exception because you can't handle it locally. But then he says // C if (condition1) handle_exception1 (); so if it can be handled locally in C it can also be in C++, so why throw anything? Also, and I'm not a C++ guy (a little exposure many years ago) so someone correct me, but if you look at part 2 of this article (link at bottom to https://250bpm.com/blog:8/ https://250bpm.com/blog:8/) he then complains about having to have helper data structures // c++ class person { int age; int weight; }; std::list <person*> people; vs // C struct person { struct person *prev; struct person *next; int age; int weight; }; struct { struct person *first; struct person *last; } people; "First thing to note is that the C++ solution allocates twice the number of memory chunks compared to the C solution. For each element of the list there's a little helper object created." [he provides a diagram too] But surely you can roll those helper objects into the main structure with templates - that is possible, right? OK, you don't use the library util 'std::list <person*> people' but still, it's possible and straightforward, right? Edit: and this was possible in 2012's version of C++ too I'm sure.
- idorosen 4y agoYes, there was boost::intrusive_list for the latter since before 2012, for example.
- leni536 4y agoI don't get it, just use `list<person>`. The prev and next pointers live next to the person objects right in the same node, no need for intrusive pointer. edit: The author assesses that this is somehow bad for person objects that are expensive to copy. But std::list has splicing operations, and since C++11 it has emplace as well, so this doesn't apply.
- esprehn 4y agoThere's no way to use that optimization if you need to be in two or more lists at the same time.
- a_c 4y agoGlad to see zeromq being brought up. It is a fine piece of software with exceptional performance. The popularity has waned in recent year, particularly after the passing of Pieter, a main advocate of this technology. I think people now use more battery including solution, probably a task queue, something SaaS'd even. I wonder how popular is zeromq nowadays? Does your company use it, for what?
- dhon_ 4y agoIs there something like zeromq (or build on it) that has guaranteed delivery?
- nlitened 4y agoYou just send the message again periodically forever until you receive an acknowledgement message. I don't think you need "something" for this, it's a five-liner. Alternatively, depending on your use case, you mark messages with sequence numbers, and fill the gaps upon receiver's request.
- benreesman 4y agoAt-most-once delivery is easy in anything. At-least-once is hard from scratch but there are good tools (TCP, HTTP/3) and patterns (retry, acknowledgement, logical monotonicity) and it’s generally not back-breaking. Of course what everyone wants is exactly-once, which is profoundly difficult even when you know the exact cast of characters in terms of problem domain and compute fabric. Idempotent updates with retry and ack is a very popular compromise that’s usually pretty feasible.
- nlitened 4y agoI always use ZeroMQ as a messaging layer among components. It's simple, reliable, supports multiple languages, provides very high performance. And straightens your mind, too: by providing a limited set of bullet-proof topologies, it really makes you think about your message flow, and when you make all lego-pieces fit, you get software that runs with _years_ of uptime.
- zerof1l 4y agoHis argument about undefined behaviors makes no sense. Both C and C++ specs contain ambiguity and leave things up to the compiler.
- 0xbeefeed 4y agoIt’s fun to see a bunch of nobodies criticizing one of the best programmers of our generation.
- hardware2win 4y agoWhat makes you think so? Genuine question
- deleted 4y ago[deleted]
- primeblue 4y ago
- tomohawk 4y agoReading a lot of the responses here makes me think that there is a misunderstanding of what the author is saying. He is talking about infrastructure code, not application code. In application code, the goal is to reduce risk to your app by shedding scope so that the app only does what it needs to do to be successful. Where does that scope get shed to? It gets shed to infrastructure. Infrastructure code only exists to reduce risk to applications. To be successful, it has to accept risk and handle it. This requires a different way of thinking, and a different way of coding. If an app doesn't handle an exceptional condition ok, there's someone there running it - they can just run it again. Or there's infrastructure that will run it again. In something like zeromq, that is not a use case.
- bluetomcat 4y agoExactly this. An application implies something that has an interactive UI. Most diversions from the “happy path” can be handled by displaying a descriptive text message, informing the user that something in the environment is not right. Exceptions are a natural fit for such problem domains because the application code can be kept relatively straightforward, unencumbered by error-handling branches. OTOH, infrastructure code that runs as a service must detect every possible error as early as possible and handle it is an ordinary operating condition.
- sylware 4y agoYep, and this is not actually the main reason not to use c++ forever ever. A c++ compiler, even naive, is out of reach from a small team of average skilled devs or individual dev and that, in a reasonable amount of time and effort. And it is solely due to its abysmally complex syntax (btw, its ABI is a nightmare too). This is an absolute and unquestionable truth, not subject to any opinion. This reason alone is sufficient to avoid c++ and the strength of the words are fair. The bazillions of other reasons to avoid c++ are just cherries on top. One of my favorites is the tendancy of devs to design software maximizing the usage of the syntax, which, due to the syntax abysmal complexity, create beyond sanity software Rube Goldberg Machines. And C has already a way too rich syntax, it is just one of the less worse compromises. I seriously start to think that open source communities should move to assembly, namely to maintain several assembly code paths (one for each pertinent hardware arch), and only have a C reference implementation and or binary specs if pertinent. I am personally trying to move towards that, with more and less success: I did the mistake to abuse, seriously, the macro preprocessor of the assembler of few times, and while looking at the mirror I saw exactly what I don't want to become.
- spacechild1 4y ago> EDIT: Assume that the object to be contained in the list is non-Assignable as is the case with any non-trivial objects, for example those holding large memory buffers, file descriptors, threads etc. In C++11 this is solved with move semantics resp. std::list::emplace_*. Also, nothing prevents you from using intrusive lists in C++.
- hardware2win 4y ago>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. However, I've decided not to use exceptions. So we have to go for something like this: class foo { public: foo (); int init (); ... }; >When you create an instance of the class, constructor is called (which cannot fail) and then you explicitly call init function (which can fail). Hmm? Use Builder and if you can then also Result<T>
- kevincox 4y agoThe best option here is a private constructor with public static "constructor functions" which can return something like Result<T>.
- m3047 4y agoDinosaur here. The VAX architecture had call stack as well as exception handling essentially implemented in silicon: there was a place for an exception handler in each call frame (at least that's how the kernel implemented it for VMS). It would go up the stack looking for a handler, call it, and then return to executing code where the exception was thrown; or at least that was an option. (Some page faults were handled this way.) I've looked for this in a language implementation ever since, in vain. Why isn't "I fixed it, try again" ever an option? (And I consider myself a C programmer, not a C++ programmer, although I acknowledge that most C compilers these days are really C++ compilers.)
- jasomill 4y agoThe Common Lisp condition system[1] supports this. [1] https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node312.html https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node312.html
- jasomill 4y agoAs a more direct analogue of your VMS example, Windows provides system-level support for handling exceptions without unwinding the stack. Here's an example of page fault handling, using Microsoft C/C++ compiler extensions to access Windows' built-in exception handling, https://docs.microsoft.com/en-us/windows/win32/memory/reserving-and-committing-memory#code-try-0 https://docs.microsoft.com/en-us/windows/win32/memory/reserv...
- t6jvcereio 4y agoIsn't it a bit arrogant to dismiss the creator of ZMQ as "the author complains about his own misunderstanding" without explaining exactly what is misunderstood or how it would be understood correctly? This isn't a random guy off the street...