Live data from Hacker News

Why should I have written ZeroMQ in C, not C++ (2012)

250bpm.com

21–30 of 170 posts

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#21
I think there's a lot of merit to this post, but it really shows its age. Modern C++ typically suffers from only the last point raised - exceptions during destruction, but that goes against the all good C++ practices.

Overall, the author's approach to exception use is flawed. It's bad form to use exceptions for code flow purposes, as per their example. If you throw an exception in the same block that catches the exception, you've wasted a whole lot of time doing something an IF-statement could solve trivially.

All projects that I have worked on that use C++ has declared that exceptions should be used in exceptional circumstances only - that is, when you cannot handle the error in the current code block and the function interface does not support returning enough information to the caller to detail what went wrong. An exception is more than just throwing your hands in the air and saying "I can't do that" - it has context, purpose, history. Getting rid of exceptions breaks some of the fundamental design concepts of the language.

2-step initialisation is the only way around errors during construction whilst forbidding exceptions. But 2-step init breaks a fundamental rule of RAII - the constructor acquires the resource and establishes all class invariants or throws an exception if that cannot be done. If the constructor is not allowed to throw exceptions, as per the language's standardised interface for constructors, then my library or application needs to be modified to support whatever process a third party library has defined as appropriate. There is a wide surface for bugs to creep in, let alone costing me time, money and effort in supporting whatever interface they've come up with.

If an object has been constructed, it should be in a valid state unless an exception has been thrown, in which case I'm told what went wrong. If I can fix it before the stack is fully unwound, I can save the day. but if there's nothing I can do, the exception has to roll to the top as that's the only other option. That then begs the question; should I catch all exceptions at the top level and prevent crashing, or should I crash and allow whatever system I'm running under to restart me? That depends on the project, but typically I'd let it go. systemd should bring me back, docker should restart me, kubernetes should restart my pod, etc etc. If I focus on what is within my control, and delegate everything else, the system will be cleaner and much more maintainable.

I've never come across a situation where exceptions during destruction is a problem, but am very interested in any examples. C++ standards define that you _can_ throw exceptions, but you shouldn't for the exact reason raised in the article - the process will be terminated as there's nothing else that can be done. If there aren't any destructors containing the throw keyword, it's not likely to throw an exception - OOM or other system exceptions are still possible, but why are you allocating memory in a destructor? destructors just need to release resources and clear down the object, it shouldn't be requesting more resources. Thinking about saving the object state before exit? wrong place to do it.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#22
1. That "C equivalent" for error handling is not an equivalent at all. If one could handle the error in the same function then there is no reason to throw. The "C equivalent" is returning an error code, which has other problems.

2. One can handle errors in initialization without exceptions and half-initialized objects: Make your constructor private and expose a static member function that returns an optional (where T is the given class).

3. Throwing in destructors is not a C++ problem, it's a general semantic problem around resources that aren't guaranteed to be freed up successfully. They are a pain in any language and you can only do best-effort approaches for not leaking them.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#23
post #6

It seems like the author's main complaints are avoided if one follows the Google C++ style guide, which says: - Don't use C++ exceptions - Don't do work in constructors (prefer "Init" or factory functions instead) https://google.github.io/styleguide/cppguide.html There may be other reasons to prefer C over C++, but if you don't like exceptions, you don't have to use them.

The google style guide says “ On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code...” and they use a lot of existing code.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#24

I've always felt that object-oriented programming as a concept (that is, define structures and then define functions to operate on them together) is useful: object-oriented programming as a language design is not. Once you understand what encapsulation, inheritance and polymorphism are , you're much better off using them as guidance to structure your program in an otherwise procedural language like C than wrestling w…

Well, using C++ as a C with objects has always been a choice. One I personally believe in. That means you basically write a C program, then when it makes sense you create c++ classes/templates as needed.

At first this sounds like the C++ features are infrequently used, but the last major project I wrote like this, probably 90% of the code was encapsulated. But there was very little class->class communication outside of a few global classes due to the use of a dbus like abstraction allowing all the individual classes to communicate out of band. (sort of like a collection of micro services if you will all running in the same process as different threads using a global message broker).

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#25
post #12

This article is simultaneously both obsolete and completely current. Obsolete in that the C++ committee has addressed almost all of the issues raised (e.g. constructor semantics, xception semantics et al) though the discussion of site-of-error/handling-of-error continues unabated. Later versions of C++ (17and 20) are powerful and expressive systems programming languages that aren’t like the object-oriented messes of…

True, I recently returned to C++ after many years absence and I'm thrilled with all the good stuff that has been added BUT it took me quite a while to learn how I'm going to unlearn/relearn my old practices.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#26
post #14
post #13

I was trying to figure out if this was satire. C++ gives you strictly more tools, particularly for guaranteeing reliability, and they're all optional. What particularly laughable here is the split ctor/init pattern is allegedly driving them towards C, whereas C doesn't even give you dtors! If you're forgetting your init calls, are you really telling me you're not going to forget your destructor call? There are plenty…

One good alternative to ctor+init() is a static factory method with a private ctor. It's just as simple as in C.

Not sure what a "factory method" is, but

    struct Foo {
      private: 
        int a = 0, b = 0;
        Foo() = default;
        Foo(int a, int b): a(a), b(b) {}
      public:
        static Foo init_zeroed() { return Foo{}; }
        static Foo init_ones() { return Foo{1, 1}; }
    }; 
    
will do. You can add as many static methods acting as "named constructors" as you want.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#27
post #14

Earlier quoted context omitted.

One good alternative to ctor+init() is a static factory method with a private ctor. It's just as simple as in C.

Not sure what a "factory method" is, but struct Foo { private: int a = 0, b = 0; Foo() = default; Foo(int a, int b): a(a), b(b) {} public: static Foo init_zeroed() { return Foo{}; } static Foo init_ones() { return Foo{1, 1}; } }; will do. You can add as many static methods acting as "named constructors" as you want.

...and now you know what a "factory method" is.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#28
post #22

1. That "C equivalent" for error handling is not an equivalent at all. If one could handle the error in the same function then there is no reason to throw. The "C equivalent" is returning an error code, which has other problems. 2. One can handle errors in initialization without exceptions and half-initialized objects: Make your constructor private and expose a static member function that returns an optional (where T…

> 3. Throwing in destructors is not a C++ problem, it's a general semantic problem around resources that aren't guaranteed to be freed up successfully. They are a pain in any language and you can only do best-effort approaches for not leaking them.

C++ is quite good here, arguably quite better than Rust, since in C++ you can add a `noexcept(true)` clause to your destructor, and if it throws, your program terminates.

In Rust, you cannot really do that (e.g. catch_unwind won't catch exceptions thrown by sub-object destructors).

And well, C++ tries to completely ban throwing destructors by default, while Rust tries to support that as much as possible.. with the most common consequence being memory leaks..

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#29
post #12

This article is simultaneously both obsolete and completely current. Obsolete in that the C++ committee has addressed almost all of the issues raised (e.g. constructor semantics, xception semantics et al) though the discussion of site-of-error/handling-of-error continues unabated. Later versions of C++ (17and 20) are powerful and expressive systems programming languages that aren’t like the object-oriented messes of…

I haven’t written anything nontrivial in C++ for many years and so far have only learned and used a couple minor features newer than C++11. Would you be kind enough to point to some resources on why C++17/20 are “powerful and expressive systems programming languages that aren’t like the object-oriented messes of old”?

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#30

Author asserts that "The decoupling between raising of the exception and handling it, that makes avoiding failures so easy in C++, makes it virtually impossible to guarantee that the program never runs info undefined behaviour." (and all the woes he encounters afterwards stems from trying to avoid exceptions due to that assertion). But that is not my experience at all. Exceptions are much more reliable than error cod…

> there will always be a case where you forget to propagate an error you got

Not if the ecosystem supports Result types a la Rust (and others). You don't get to access the return value unless you deal with handling the error first.

Post reply on HN