Hacker News new | past | comments | ask | show | jobs | submit login
Why should I have written ZeroMQ in C, not C++ (2012) (250bpm.com)
140 points by endorphine on Aug 12, 2022 | hide | past | favorite | 103 comments



Read 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/) 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.


I 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.


There's no way to use that optimization if you need to be in two or more lists at the same time.


Which is also an issue in the author's example, no? So the point would be moot.


Wouldn't std shared pointer handle that case?


Was it available when ZeroMQ was being planned? It's not a very new project.


boost shared_ptr has been around since 2001. ZeroMQ was started in 2007.


I honestly think exceptions will go down in history as a design mistake. Newer languages (eg Rust, Go) doesn't have them.

Exceptions make it incredibly difficult to be able to follow program flow. Most of the time you just log an exception and then either move on or exit. There's almost no actual handling of an except (other than maybe retrying). It gets even worse when people start using exceptions for control flow rather than errors.

One note about your example:

    > std::list <person*> people;
You wouldn't do this in C++. Why? Because ownership isn't clear meaning if people is cleaned up, it won't clean up the elements automatically. You could do:

    std::list<person> people;
but you have to be aware of copy semantics. You might do:

    std::list<std::unique_ptr<person>> people;
but that gets awkward too.


The differences between Go and Rust error handling is that Go doesn’t force you to handle its errors. Nothing bad happens if you just forget or don’t handle the error.

In Rust if you just unwrap it’s going to panic and in exception based languages it’s going to propagate until someone handles the error.

I personally don’t think Go error handling is going to go down in history as good.


I agree Rust's error handling is better and you can criticize some decisions in Go's error handling. I mean it also has these weird hybrid exception-like things (ie panics).

But the main point is that Go (and Rust) moved away from exception-as-control-flow that C++, C# and Java had adopted.


"exception-as-control-flow" is a misuse of exceptions. You can catch them just at strategic points and ignore them everywhere else.


I don't like Go error handling either (and I do like Rust) - but if the argument is against exceptions because of confusing flow, then there's a case for it in some languages I think - e.g. it's what you'd choose for Python if nobody had come up with exceptions (or they fell out of favour in time) for example.


Aside for not having to handle errors in Go, the type system itself makes error handling less useful. Rust's error handling is superior to Go's in every way I can think of.


It's literally impossible to write concurrent and highly-available code without exceptions.

You will be forced to write your own stack bubbling code. It's 2022, please don't write your own hand-made bespoke stack unwinding.


I think the GP was complaining that exceptions exist in the language, not suggesting that you can avoid them in C++. That’s why the other languages were listed.


> It's literally impossible to write concurrent and highly-available code without exceptions.

Do you have an example?


Yes, there was boost::intrusive_list for the latter since before 2012, for example.


C++'s throw lets you postpone handling of erroneous condition. You throw, then sort. You can postpone sorting of causes indefinitely, basically, up to the level where all of context needed is lost.

In C, you cannot throw. You have to handle. There will be more "if" statements, that's price to pay.

I work on code in C and C++ in more or less close problem domains and C++ provokes style and/or design that is much more hard to work with.

One of culprits is deep inheritance - once built, the inheritance structure cannot be changed. And I saw inheritance trees as deep as 10 recently. Instead of refactoring, I see copy-paste.


The point is not that you're handling an exception with some function, it's that knowing which function is being called to handle the exception becomes difficult to reason about.

This is the relevant line in the article:

> The problem with that is that you have no idea of who and where is going to handle the exception.

If you don't have a try/catch immediately around the throw, say at a higher level of where the function was called or, in the "worst" case, in the main body block, then the handling becomes hard to reason about.

I believe the argument is that reasoning about who handles which exception when is difficult and putting in logic to handle each individual exception at a higher level increases program complexity. Further, hunting back through the code to which piece of parent call handles the exception becomes increasingly difficult, especially if a function is called at different points with their own try/catch blocks.

The problem compounds because if you use classes, then there's no way to get around using exceptions as that's the only way to communicate that a constructor has failed. This means if you wanted to abandon C++ exceptions in favor of C-style error handling but still wanted to use classes, you would now need to mix and match C-style error handling with C++ style exception handling.

I don't quite see the argument against the class/struct <list> issue and I agree that this, from what I can tell, could be worked around and has better alternatives in C++ than they're making arguments against.


> The problem with that is that you have no idea of who and where is going to handle the exception.

I don't buy the argument.

By design exception handlers are handlers of exceptional events, which due to their exceptional nature either lead to a controlled recovery or controlled exit.

Once you're faced with your personal need to care enough about specific types of exceptions to the point they are no longer exceptional but a common occurrence, you're already mishandling exceptions at a fundamental level.

Consequently the solution is to not handle these expected cases as exceptions, or at most add try-catch next to the potential source of these commonly-occurring exceptions and don't let them leak beyond their source.

Exceptions are a kin to fire alarms and exception-handling is a kin to an organized evacuation plan. If you're misusing the fire alarm to the point you're complaining that you pull a particular fire alarm so often during your normal workflow that you start complaining you can't track badges during emergency evacuations, you should take a step back and take a long hard look at what you're doing, and how you're misusing that safety feature.


> By design exception handlers are handlers of exceptional events, which due to their exceptional nature either lead to a controlled recovery or controlled exit.

Exceptions are commonly used for events that are not really exceptional. In Java there is a precise distinction about them (checked vs unchecked exceptions), in C++ not.

Commonly libraries (not the C++ stl to be fair that rarely uses exceptions) use exception for things that are expected during a program execution: a file does not exist, a network socket closes, a resource is not available, the user interrupts the program, etc.

You may say: well, you should use exceptions for the first kind of error handling and error codes for the second, problem is that adds even more confusion, since you have some error conditions that may throw an exception and some other that may return an error code!

The simple solution is to not use exceptions at all, and the best way to do so is to use a language that doesn't have exceptions (such as C, but also Go, Rust, Zig, in general the trend for new languages seems to be to not have exceptions, and probably there is a good reason behind). While most C++ compilers let you can disable exceptions, but that will turn any throw of an exception into a panic or worse undefined behavior for code that uses them.

Especially in C++ exceptions are even implemented in a way that makes them more dangerous to use compared to other languages (such as Java):

- there is no indication about the fact that a method may throw an exception, rather there is the key noexcept that signal the contrary (that is all methods may throw exception except the one marked with noexcept, and there is no way to know which kind of exceptions a method may throw) - you are not forced to handle exceptions (in Java for checked exceptions you either handle them locally or have to declare in the signature of the method that it may throw exception) - exception introduce an hidden flow of control that may create various bugs impossible to spot (especially if this hidden flow of control passes trough libraries that you don't know what they do, and may for example have allocated resources that are not freed during the stack unwinding).

Having exceptions in low-level languages to me is a bad idea. Having them in higher level languages it's less of a bad idea because you have other form of protections, but still you can avoid them even there (I tend to not use exceptions in TypeScript, and prefer to return a Result type, and wrap any standard library function that may throw exception in this)


> in general the trend for new languages seems to be to not have exceptions

There is also a parallel opposite trend happening in (the obviously not mainstream) "effect" languages, such as koka and eff.

The new theory of algebraic effect handlers will probably trickle down to the new mainstream languages of the 2030's or 2040's.


I don't really know what to say. It's not that you're wrong or couldn't' be right, it's that there's a whole ecosystem of language and tooling, culture of idiomatic program design and human psychology at play.

So you draw a line in the sand and say that using exceptions for cases where errors are commonplace is fundamentally misunderstanding. Is this true for every library you use? For every programmer you're going to hire? For every scenario that you're going to encounter? Maybe, but it's asserting what reality should look like rather than what it actually is.

Arguments about which programming to use on a philosophical level are always hard because it's, in my opinion, effectively a psychological argument. The language provides intrinsics that allow you to think about problems in a certain way and use tools to help express ideas within that framework succinctly. So saying "this is a fundamental misunderstanding of what this intrinsic is there for" is kind of besides the point. How is it used? Does it's presence in the language make sense to use it in the way you think it should be used?

I actually don't have a big opinion on this, I was mostly trying to "strongman" the argument so that a better discussion could be had. I will point out that many more "modern" languages, like Go (and Rust?) use the more C-like error handling than the try/catch type error handling.


Exceptions are typically understood to apply to exceptional events that just shouldn't happen. The only language I know of that treats them as control structures is Python (ok, lisp as well come to think of, but they seem to be very different things from C++ exceptions. I have no experience with these).

> Arguments about which programming to use on a philosophical level are always hard because it's, in my opinion, effectively a psychological argument

If you treat them other than language convention expects, maybe that's a problem.

I have read up on the original exception designs and talked to the originator of these (Brian someone, I can't remember his surname). I believe I'm not misrepresenting him to say that he looked on exceptions as 'should never happen' events. However they have come to be seen as treating errors a bit more forgivingly (eg. network dropped) so in some cases a fresh attempt can be made. That seems reasonable.

As others have said, they're your last ditch attempt at doing something sensible, or perhaps trying to recover, when the impossible happens, so sqrt(-1) is an exception. Excepting because of a failure of a user to log in is not (true: that comes from a dr. dobbs article).

This is a good discussion https://en.wikipedia.org/wiki/Exception_(computer_science).


Doing a quick Google search, I see a paper by Stroustrup that looks to support your argument [0]. From the paper:

> One aspect of the exception handling scheme ... is that the default response to an error ... is to terminate the program.

While that may have been the intent, I'm arguing that how it's used is not that in modern C++.

For example, the constructor for `ifstream` throws an exception when encountering a problem trying to open a file [1]. There are many cases where this wouldn't fall under your definition of "exceptions should lead to termination". Whether this is "proper" C++ standard library ideology or not is beside the point. This is built into the language, used in a standard library and guiding programmers into an idiomatic way of using exceptions as error handling.

I imagine the C++ STL is rife with this type of idiom.

I'm really not a big fan of "you should read the article" type of arguments, especially when the article contradicts the point. From the Wikipedia article you link to, here some quotes that support my thesis:

> Kiniry writes that "As any Java programmer knows, the volume of try catch code in a typical Java application is sometimes larger than the comparable code necessary for explicit formal parameter and return value checking in other languages that do not have checked exceptions. ..."

> Exception handling is often not handled correctly in software, especially when there are multiple sources of exceptions; data flow analysis of 5 million lines of Java code found over 1300 exception handling defects.

> ... Weimer and Necula wrote ... that they "create hidden control-flow paths that are difficult for programmers to reason about".

These mostly relate to Java-style try/catch issues but I suspect they're at least somewhat valid for C++.

Again, I'm trying to make a good-faith argument. I see many situations where exceptions as understood by yourself and Stroustrup can lead to better quality code but I'm trying to highlight what the original article was talking about and try to see it from their point of view.

As with almost all of these arguments, there's an "it depends" clause. The arguments against exceptions in the article has an implicit "it depends on the context of creating my MQ library". The arguments in the comments are arguing against it without providing the necessary context.

[0] https://www.stroustrup.com/except89.pdf

[1] https://en.cppreference.com/w/cpp/io/basic_ios/exceptions


This is getting badly OT and I don't know how we got here. My original top post was saying if you don't need exceptions, why are you using them. They weren't necessary there so they should not have been used there. That is all. In this I am sure we agree.


> I don't really know what to say. It's not that you're wrong or couldn't' be right, it's that there's a whole ecosystem of language and tooling, culture of idiomatic program design and human psychology at play.

It seems you wanted to say that there are frameworks out there that use exception handling to handle flow control.

There are indeed. There's also plenty of fundamentally broken code around. That is not a justification to perpetuate mistakes. It's like someone complaining that sticking fingers in power outlets hurts because of the jolt. Well, then don't?


To me the two bits of code in the error handling case or not comparable. In the c++ case you would still handle the error in the if statement if it can be handled otherwise you would throw it. C++ has the advantages of giving you both options.


> If you don't have a try/catch immediately around the throw, say at a higher level of where the function was called or, in the "worst" case, in the main body block, then the handling becomes hard to reason about.

what exactly do you call "hard to reason about" ? if it's an exception most likely the library itself has no business handling it and should just let it propagate to the user of the library.


I don't want to be the user of a library that might do something even the library itself doesn't know why it happened.

Your point seems to be that any exeption that makes it to the top must by simple definition of reaching that point, have been something the user did or in some other way "not my problem", which I think is ridiculous.

The whole article starts with the assertion that they want this library to be solid not slapdash.


A 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.


Can 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.


The 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


wouldn'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 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.


LOL, you haven't actually tried using C++ without exceptions have you?


I 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.


>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.


I know how noexcept works, some of the other commenters do and some don’t seem to. It’s clearly not “enough” in the sense that error handling is almost invariably one of the trickiest bits of language design, and I don’t think anyone is arguing that C++ leads the pack on it.

My assertion is that C++ is entirely workable with few concessions to the legacy of exception design, and it seemed like less of an appeal to authority to cite first-hand experience than to point out that Google has been doing it successfully for 20 years.


Sure. Embedded programming.


Mostly 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.)


How'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")


-fno-exceptions should be sufficient for this, especially if you link statically. People also use `-fno-rtti` to save the extra data by dynamic_cast etc.


Not 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.


I 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.


And std::optional can really help with the semantics of that Result type.


The 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.


That's a lot of excessive code to write just to avoid using exceptions though.


Eventually you'll be able to use std::expected in C++23!

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/

"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


I 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.


Pretty debatable. C++ was always a pretty big language. Maintaining a decades-old project is going to require either ongoing modernization or you’ll most likely end up touching a lot of it, but you can, and the “start over” option proposed so frequently around here is not for organizations with tens of thousands of engineer years in their codebase.

New stuff that doesn’t need your decade-plus of C++ libraries or hit a few other corner cases should probably be in Rust, but you’re back to, that’s mostly greenfield stuff.

If you are beholden to not starting from scratch, modern C++ heavily subsets the language and is rather pleasant, certainly the tooling is best-in-class (which is often overlooked).

I consider it a good thing that C++ still compiles code from decades ago, and it’s frankly astonishing how well it’s aged when you consider the domains it targets.

Ditching everything every few years is nice work if you can get it, and this being HN that’s going to be a vocal bloc, but usually you get to greenfield everything because something else failed.


That'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.


Just like any other language, Python 3 will be a study case for generations for what happens when one takes features away.


Does C++23 have something like Rust's ? for std::expected?


It’s extremely non-obvious how to do the Maybe and Either type classes in C++ in a truly ergonomic way.

Rust wiring in syntax for this (which is what ?-notation is, it’s special-case do-notation) is both a blessing and a curse: it’s extremely useful today and a breath of fresh air relative to C++ or Java or whatever but this scenario has been seen before: GHC had do-notation and return, which are dramatically more flexible than ?-notation, wired in pretty deep when people started realizing that you want Functor => Applicative => Monad with pure === return and ApplicativeDo. They’re still trying to get the migration done today and they started years and years ago.

Time will tell how Rust will age as it inevitably continues adding algebraic power, but superficially ?-notation seems doomed to end up a kind of dangling appendage that will be obsoleted by some more general notation for algebraic effects.


That's interesting. Having learned Haskell long before Rust (and having been programming in C++ for a long time) I found Rust's ?-notation to be annoyingly limited due to the reasons you gave, and also annoying syntactically. I saw in many Rust sources it's often tucked away at the end of lines, flush against the last character, a single character which changes the meaning of the line, well, the whole function, but is often lost to me visually, and I have to scan the line to see it. But I guess people got used to it or just don't bother parsing all characters in a line of code like I do.

When you say "They’re still trying to get the migration done today... " you mean Rust's core developers, right? Is there anywhere I can read up on it?


Ah no the migration I was referring to is `Applicative`: I just upgraded our "primary" Nix `devShell` to use `9.2.4` (`RecordDotSyntax` is finally here, may the thousand-year reign of the Glorious Glasgow Haskell Compiler commence! /s) and it's hitting me with deprecation notices about `return = ...` rather than `return = pure` and/or omitting and explicit `return`. Which makes sense, the rationale is at https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/monad-of....

I remember when I was working on `Haxl` at FB in like, 2015 or 2016, it needed `ApplicativeDo` and that was like, quite the novelty in industrial Haskell (I think it was already a no-brainer to the research/OSS/etc. crowd). So mainstreaming `Applicative` into the effects hierarchy has been going on for 6 or 7 years that I know about, and I bet a lot longer than that (the paper introducing `Applicative` is from 2008: https://www.staff.city.ac.uk/~ross/papers/Applicative.html).

There's an (admittedly oversimplified) sales deck for algebraic effects that I have found really effective at selling this stuff to open-minded non-FP hackers: https://philipnilsson.github.io/Badness10k/posts/2017-05-07-....

At the risk of inviting an old-fashioned HN brigade gang-tackle: I think it indirectly illustrates why Rust is going to end up going all-in and ditching the ?-notation. Please don't hurt me.


There is ongoing work to generalise ? operator in Rust with traits[1]. If it succeeds, current code is not going to be obsoleted by the new solution, only extended.

[1]: <https://doc.rust-lang.org/std/ops/trait.Try.html>


If compound statements every get standardized then you could write a TRY macro that worked pretty similarly.

https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html



Yes we've been using this. Unfortunately it is not standard and iiuc msvc has no equivalent?


Exceptions.


Exceptions are nothing like Rust's Result.

- exceptions don't appear in the signature of a function

- you don't have to handle exceptions explicitly, while you cannot access the value of a Result without explicit error handling

- Exceptions are implemented like Rust's panic. The error path in exception is more costly, while the nominal path is supposedly less so, due to less register and branch pressure than the result type that at least has a discriminant to check.

Exceptions are such a different way or handling errors from Result that my team finds itself reimplementing Result types in C++ again and again. Alas, it is neither standard nor ergonomic in C++


There is also Boost Outcome


To 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.


The 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.


Just mark all destructors noexcept?


Noexcept 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.


Destructors already are noexcept by default


Glad 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?


I 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.


Is there something like zeromq (or build on it) that has guaranteed delivery?


You 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.


At-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.


C++ 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.


Reading 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.


Exactly 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.


`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.


I'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.


We 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.


I 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


> We used init functions at my last job

has the factory pattern somehow managed to crawl into a hole?


init functions and factory pattern are not orthogonal.


that is not the statement. they say C++ sucks because init pattern means an unititialized object might be used. the factory pattern solves that


I'm guessing it got posted because another posting had a link to it... or at least that's why I ran into this for the second time today.

I was just contemplating C vs. C++ today. I've been doing Go for some time and I want to build something relatively small and performant. I think I'll keep some C++ for myself here.


>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>


The best option here is a private constructor with public static "constructor functions" which can return something like Result<T>.


I'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.


The 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.


Good thing cleanup code can never fail, what with there being no way to report failure from a destructor.


Destructors can log. Destructors can set flags.


At which point you're dealing with errno C error handling anyway.


C++ 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.


His argument about undefined behaviors makes no sense. Both C and C++ specs contain ambiguity and leave things up to the compiler.


Dinosaur 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.)


The Common Lisp condition system[1] supports this.

[1] https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node312.html


As 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/reserv...


This essay has not aged at all well.


> 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++.


C++ 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.


Isn'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...


It’s fun to see a bunch of nobodies criticizing one of the best programmers of our generation.


What makes you think so?

Genuine question


Yep, 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.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: