Hacker News new | past | comments | ask | show | jobs | submit login

That the author didn't understand how to write robust C++ by 2012 (after 5 years) without constraining themselves unnecessarily to C is not a compelling reason to prefer C over C++.

"Doctor it hurts when I do this." Well stop doing that then. Learn what works and what doesn't, but don't throw the baby out with the bathwater.




Yeah it seems... odd. I fully get preferring return codes to exceptions, but you can still just do that in C++? Heck, you can trivially do a type-safe union of return value or error code in the spirit of Rust in C++.

And you can do things like enforce that the return value is used by checking in the destructor if the return value was unwrapped.

Similarly for constructors if you have an object that can fail construction instead of doing what the author suggested of

   class foo {
   public:
       foo ();
       int init ();
       ...
   };
You could instead do something better like:

   class foo {
   private:
       foo ();
       int init ();
       ...
   public:
      static std::optional<std::unique_ptr<Foo>> create();
   };
This is a well-established pattern in languages like Java, for example, to do exactly what the author wants - a constructor that can fail without forcing callers to just know they need to try/catch.


Of course you can do various things in various languages, but good luck getting any adoption of a library that goes against the language idioms in its language. You expect certain kind of API from C libs, certain one from C++, and different one from Java packages.

If C idioms work better in your project, just go for C. You get other advantages from that, like faster compile times and being independent from C++ stdlib, which can be messy at times (not even speaking about Boost). You don't have to stay with C++ just because somebody else (or even you) thinks that it's generally a better language.


Roughly ~50% of the C++ ecosystem compiles with -fno-exceptions[1]. So using return values instead of exceptions is not against the language's idioms at all. It's one of the reasons std::nothrow exists, after all.

Similarly while exceptions are part of the Java idioms, it does after all have checked exceptions even, it's incredibly common to not use that idiom on constructors, where it's weird. Just because a thing is an idiom in some cases doesn't mean it's the expected idiom in ALL cases.

And if an idiom is dumb it doesn't mean you should just do it to follow along with the dumb, nor should you go to a language where you're forced to be less safe & less clear just because "but mah idioms". Be the change you want to see.

1: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p070... has the survey data (along with a language proposal to make return-value exceptions a proper C++ feature)


To be fair to the author, std::optional was added in C++17, which came after this blog post was written. I agree, maybe if he had written it today his views would be different.


And yet boost optional has existed for a good half of forever. C++ programmers have been writing they're own versions (maybe, fallible, etc) for the other half.


But there have been <optional> implementations around for more than a decade


Hmm... Do you really need to wrap a pointer in std::optional?


It forces anyone trying to use it to contemplate the no-value case.


Replace the pointer with a value type and now you'd see why the optional is necessary.

Similarly I'd probably substitute the optional with a variant that has the actual error code. Go nuts with whatever you want here.


> Go nuts

Let's make error code optional, too.


You can use a returned stucts in c to pass both an err code and a value

    typedef struct
    {
       int err;
       int value;
    } rtn_t;

    rtn_t rtn = foo();
    if(rtn.err < 0)
       printf( "err=%i", rtn.err);
    else
       printf( "value = %i\n", rtn.value);


There are certain widely accepted norms on how to handle errors in C. It is best to restrain ourselves to "prior art" so that your code is immediately comfortable to read and use for a seasoned C dev. To list a few: return codes, errno and longjump


Don't use setjmp/longjmp unless you can guarantee that you own all frames between one and the other (i.e. don't use it as a public error reporting API in a library, for example). They don't play well with C++, and really anything else that needs to unwind the stack. Those who write R extensions in C++ know how messy it can be.


The only widely accepted one in that list is return codes.


> a seasoned C dev. To list a few: return codes, errno and longjump

A seasoned dev that thinks errno and longjump are good idea's should have retired 25 years ago.


Yeah, I just tried to be on the more "permissive" side of things. Though per-library errno is being used (zeromq, libuv).


> both

But only one of them would make sense at a given moment, so why waste memory?


So that at any given moment, you know which one you have.


Your example doesn't tell me which I have. It's assuming that err = 0 means success, but that's an assumption not a contract of the type.

By comparison some something like https://github.com/oktal/result will tell you if it's a success or error without any magic error codes that actually mean success.

You could do this with a type bit + union in C, it's just more painful without templates


That requires 1 bit of information, and there is often a way of embedding that bit in the value itself; but the general technique of representing alternatives is called “a tagged union.” (You can also hear the fancy terms “the product type” for the struct and “the sum type” for the union.)


Accessing that one bit can be as expensive as passing an int around.


> so why waste memory?

In c it'll be either in a register or on the stack.


Make constructors private and use (static) factory methods with meaningful names is underused, IMO.


This forces you to allocate it on the heap... The C init function didn't require this.


Have it return by value in an optional then if that's what you want. You're not forced to do it on the heap. Or if for some reason you need to be able to pick you can do the whole templated allocator song & dance.


Writing C++ like Java (or any other language for that matter) won't help anyone. The code will turn out non-idiomatic and weird for any couple of C++-eyeballs.


This is a classic design pattern. Its not language specific.


I always find this comment funny, because C++ is older than Java and many Java programming patterns originated from C++ designs.

Then again, many C++ younger devs don't even know what Booch method might be all about.


Especially since the GoF Design Patterns, which are often cited as the main reason for Java being verbose, were described in the original book using C++ (and smalltalk) examples.

Just because they may be overused in Java doesn't mean people shouldn't use them at all in other languages.


They have originated from C++, fine, but it is a different language, different ecosystem, different community, different ideology and -- different idioms


That is the thing, doing MFC, OWL, VCL, Qt, wxWidgets, DCOM, CORBA, Motif++, BeOS based applications was hardly any different from what Java became.


Yes most of these things are terrible


The author is not an unknown entity. ZeroMQ is one of the highest performing brokerless MQs, if not the highest. I do remember that the main pain points were wrt STL and not C++ (but it's been a while since I read that post.) Other people you might respect have tasted C++ and bailed on it (eg Linus Torvalds.) For me, going to a template-less world would be a nightmare.


My respect for Linus does not extend to his opinions on programing languages. I haven't found any of Linus's objections to C++ compelling. I strongly believe that a C++ kernel developed with the same care as Linux would be strictly more robust. There are simply more and better tools for constraints and engineered safety in C++.

Besides arguments about portability, C's only "advantage" vs C++ is a lack of features. This isn't an advantage for a serious engineering project, or really any project with code review.


And if you're looking for an example of a kernel being developed in C++, look at Zircon [1] (the kernel for Google's Fuchsia project). It's certainly past the "trivial complexity" phase, and doing just fine.

And of course, things like RAII are incredibly useful when managing resources, and templates for encapsulating generic data-structure behavior in an operating system.

[1] https://fuchsia.googlesource.com/zircon


I didn't spend much time looking at it, but to me the code looks more like C than C++ lol

https://fuchsia.googlesource.com/zircon/+/master/kernel/incl... https://fuchsia.googlesource.com/zircon/+/master/kernel/kern...

Even things like SpinLock, which I was expecting to use class for RAII, looks like it was implemented using C, then with a C++ wrapper: https://fuchsia.googlesource.com/zircon/+/master/kernel/incl...

So to me (I'm no C/C++/Kernel/etc. expert) it looks like they are writing the kernel mostly in C, with C++ goodies sprinkled in some places, where it's useful (ie: RAII for spinlock guard)


That’s because Google’s style guide is very restrictive.


Lack of features can be a good thing. There will be no “language feature anxiety” for the people who is writing code. The code is easier to comprehend for new people who just joined an ongoing project, because there are no non-obvious execution flows. And C code can be compiled so much faster than C++ due to the language being simpler.


I usually spend the compilation time staring at code, only to realize, somewhere in the middle, that I have to abort the build because I must make another change in a large and complicated template in header file that is included almost everywhere. At times, I do miss C, especially its classical variant, where you often didn't even have to include anything and instead could just write the declaration of something you needed - right next to where you need it...


You can still use forward declarations. You can even use them in C++. But you have to be sure to get the signature exactly right (one of my favorite passages from “Design & Evolution of C++” involves Stroustrup’s discovery that a lot of programmers were cavalier about their forward declarations, e.g., using “...” as in any declaration where it would be syntactically valid).


> For me, going to a template-less world would be a nightmare.

I could do with just generics. I am honestly sold on the idea that OOP is flawed. I've tried to do "real" projects in C, but the continuous casting just felt like unnecessary ceremony.


"defer" or equivalent is another major missing piece.


> Other people you might respect have tasted C++ and bailed on it (eg Linus Torvalds.)

Sorry, Linus didn't say anything compelling against C++. He explained very clearly why C++ didn't fit with his own personal way to work, and that's fair.But his message did never contain anything more than this!


I get this point but if you’re using the STL then you’re going to have to deal with this stuff.

At one point hacking around these issues become a bigger cost than just using some C library

Though personally I think the abstraction ceiling on C is so low I can’t imagine that cost ever being that high, other people are more comfortable with macro-based APIs


I'd love a real world example as I've been using the STL for 14 years, and only thing I've worked around was std::map, which I implemented much like boost flat_map.

Yes, you need to be aware of object lifetimes in c++, but so do you in C. C++ makes it far easier to manage, though.


I agree. These days I can't even imagine programming in C++ without using STL. It still has some weird ergonomics at some places though. For example - instead of a simple vector.contains(something) we have to write this

  std::vector<T> x_Vec;
  if (!(x_Vec.find(another_x) == std::vector<T>::npos) {
      //some code
   }
Also the ugly std::get<N> syntax..

  auto x = std::get<0>(myTuple);
Why not -

  auto x  = myTuple.0


The first snippet you wrote makes sense for string, but not for vector. For vector, you'd need to do:

   if (x_Vec.find(another_x) != x_Vec.end()) {
      //some code
   }
The reason why vector doesn't have contains() is because it would be an O(N) operation for a vector. STL is generally designed in such a way that, when something needs to do an O(N) lookup, it has to deal with iterators, making the iteration happening in the background a bit more explicit.

But, for example, for std::set and map, you can just do:

   if (a_set.count(x)) { ... }
It's not a good pattern for multiset and multimap, since it won't stop at the first occurrence. C++20 finally added contains() for that reason - but, again, only to associative containers.


Yes, I'd need to check for end iterator in vector. But my point still stands - C++ does have some weird ergonomics.


but isn't find() an O(n) operation already?


Indeed! Which is why find() is not a member function on vector (so my snippet above is wrong - I just missed that part). It's a global function, so you need to do:

   if (find(x, v.begin(), v.end()) != v.end()) ...
For maps and sets, find is a member function though. Same principle - if it is implemented in some optimized way, the class provides it directly, but not otherwise.


If you didn't write your own generic "contains" method on the third time you had this pattern I don't know what to say


> "Doctor it hurts when I do this." Well stop doing that then.

A surprising amount of pain comes from cargo-culting/copy-pasting some behaviour because the "old way sucks" and the "new way rocks" (the opposite problem also happens, that it being stuck in old ways, or: "how do I lift this excavator? I want to dig" )


I'd suggest that the author understood it. The point he seems to be making is that writing such code in C++ was not that much better than in C. At least with regards to exceptions, boilerplate and such.




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

Search: