Hacker News new | ask | show | jobs
by badlibrarian 3 days ago
If you're in a market that requires using C++, many of these decisions are made for you by the platform above you, and you're screwed. Turn on RTTI, build a fort to deflect the random exceptions they'll throw at you, and may the gods allow you to recoup your R&D before some well-intentioned yokel in some media or game vertical changes everything and requires you to change everything.

On the other hand, if you control your own destiny and care about velocity and code quality, many of these choices eventually become self-evident.

If you are messing around with the latest and greatest esoteric C++ stuff in 2026, bless you, you beautiful nerd. But it may be time to start evaluating where you are in life, and how you got here. (And if you're on a C++ committee, I revoke those blessings.)

For those who remain: if you have a C++ code base yet somehow have enough time and energy to write opinionated blog posts, it's really hard to imagine why you think you'd have a better take on this than Google.

https://google.github.io/styleguide/cppguide.html

3 comments

> build a fort to deflect the random exceptions they'll throw at you

Sounds like you hate exceptions, right? In which case why do you handle them at all? Just leave them all unhandled and suddenly every exception is a crash. Which is really no different from someone choosing to terminate. Which you have to worry about even without exceptions.

> if you have a C++ code base yet somehow have enough time and energy to write opinionated blog posts, it's really hard to imagine why you think you'd have a better take on this than Google.

"Given that Google's existing code is not exception-tolerant [...] Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. [...] Things would probably be different if we had to do it all over again from scratch."

> Which is really no different from someone choosing to terminate.

If you std::abort(), you'll get a useful stack trace in the core dump. If you crash from an unhandled exception, you don't. That's a pretty huge difference and is one of the reasons exceptions suck.

That's nice but it's certainly not guaranteed by anything, just something provided by your toolchain or platform. ("Core dumps" aren't even a thing in C++.)

If you're looking for implementation-specific guarantees then you could make that happen with exceptions too. I think on GCC replacing a function like __cxa_throw might be sufficient to let you capture a stack trace?

If you're looking for source-level-only guarantees then another option is to just replace your throw <expr> statements with one that attaches whatever extra info you want. You could literally script this to patch your external repos automatically too. Or heck, maybe you could even just define throw to be a macro that shoves your stack trace into some global variable before actually throwing.

> If you std::abort(), you'll get a useful stack trace in the core dump. If you crash from an unhandled exception, you don't. That's a pretty huge difference and is one of the reasons exceptions suck.

All of this is up to the implementation in practice. The codebases I work on generally follow the pattern that exceptions may be thrown but may not be caught*, and thus they practically serve as terminate. And we absolutely get stack traces in our core dumps (Linux, both GCC and clang), and basically all of the complex debugging I do starts with a coredump stacktrace.

* We follow this pattern for a few reasons, (1) it is generally safer for us to assume that libraries we consume (STL included) will behave as expected with exceptions enabled, (2) "don't catch exceptions" (or if you must, catch the as close-to-throw as possible) is a simple way to avoid exceptions-for-nonexceptional-cases control flow, and (3) most of the C++ codebase is exposed through bindings in Python, and propagating errors as exceptions is the only Python-friendly way to handle it.

Just FYI, finally in C++ you can add a top-level exception handler and call boost::stacktrace::from_current_exception (https://www.boost.org/releases/1.85.0/), and get a stack trace on exit as helpful as in Python or Java.
> If you crash from an unhandled exception, you don't.

.. you absolutely get a stack trace from unhandled exception in c++, that starts where the exception is thrown? At least with clang and GCC, maybe MSVC isn't able to.

foo.cpp:

    #include <stdexcept    
    void bar() { throw std::runtime_error("boo"); }
    void foo() { bar(); }
    int main() { foo(); }

running:

    $ g++ foo.cpp -std=c++23 -g
    $ ./a.out
    terminate called after throwing an instance of 'std::runtime_error'
      what():  boo

    $ coredumpctl gdb
    ...
    #7  0x00005555555551bb in bar () at foo.cpp:3
    #8  0x00005555555551da in foo () at foo.cpp:4
    #9  0x00005555555551e6 in main () at foo.cpp:5

it's a basic example, but it's how I've always done all my debugging in C++ since forever
Sure, in a desktop program you do. In embedded exceptions are a scourge because they crash your program until someone can get back out there to power cycle it. At least with return-codes you can continue execution even if you failed to effect the change you wanted. If that was ancillary to the system’s core function then the system keeps running.
Pretty sure esp32 just rebooted automatically when unhandled exception is thrown for instance
> Sounds like you hate exceptions, right? In which case why do you handle them at all?

One of the problems with exceptions is it’s utterly impossible to know if a given function call can return exceptions and if so what they are.

My code DOES want to handle errors. Exceptions are, imho, a very very poor way to report errors.

Python is the bloody worst because I never effing know what the hell any damn function can throw or return. It’s so so frustrating.

Error handling is a hard and unsolved problem. But I’ll take Rust results over exceptions 10,000% of the time. Not even a question.

Exceptions aren't meant to report errors, just in general. That's a misuse of them. Exceptions are meant to be thrown when a contract cannot be fulfilled. Yes, you're unable to know what exceptions a function may throw. That's the way it should be, because exceptions aren't supposed to be part of the function's contract.

For example, you're implementing an arithmetic operator and have reached an erroneous state, but the arithmetic type doesn't have an error value, the only way to communicate the error is by throwing. Another example: you've specified that a function must always succeed, but later on you find a case where the function cannot succeed. Instead of fixing all the possible call sites, throw an exception. All those callers could not have handled the error anyway, because they were coded under the assumption that no error would happen at that point. Throwing an exception and letting it unwind the stack way up (perhaps even all the way up to main()) is the sensible solution, because at that point you've reached a situation with no reasonable way for that code to handle.

Saying that you prefer Result over exceptions is like saying that you prefer strings to functions. They do different things. If you like Result, nothing prevents you from implementing a C++ equivalent.

> Exceptions aren't meant to report errors, just in general. That's a misuse of them. Exceptions are meant to be thrown when a contract cannot be fulfilled. Yes, you're unable to know what exceptions a function may throw. That's the way it should be, because exceptions aren't supposed to be part of the function's contract.

I don't think these are true? What about std::vector::at(), std::optional::value(), etc.? And then there's std::system_error.

>std::vector::at(), std::optional::value()

Both functions must return T &. If the vector is not long enough, or the object is not set, then returning a T & is impossible. So we have a function that has already been called and which must return something valid, and cannot return something valid. The only two ways to resolve this contradiction is to throw, or to terminate.

(Well, you could also trigger undefined behavior like operator[]() and operator*(). No comment.)

>And then there's std::system_error.

And what am I supposed to conclude from the existence of a type?

> The only two ways to resolve this contradiction is to throw, or to terminate.

Or they’re bad APIs that should be redesigned to be not bad.

They’re fallible functions. Don’t write fallible APIs that require exceptions to report errors! That’s bad API design!

I think you missed my point. I was referring to the fact that some of these standard exceptions are very much a part of the contracts of their respective functions. In fact, that's their entire point. This directly contradicts what you wrote.
> Throwing an exception and letting it unwind the stack way up (perhaps even all the way up to main()) is the sensible solution

No. I would never in a million years do this.

If the API is that a function is infallible and then I decide that it’s a fallible function then that’s a pretty major change and I’m just gonna have to update all the call sites to deal with a fallible return result.

Saying throw an exception and bubble up to main provides just about zero value. Might as well just call std::abort. Which is also something I would never do.

> Saying that you prefer Result over exceptions is like saying that you prefer strings to functions. They do different things.

So here’s the thing. In 20+ professional years as a C++ dev I have never ever once worked in a codebase where exceptions were used. Certainly never in first party code. Only when dealing with annoying thirdparty libraries that leveraged them.

I think your comment “contract can’t be fulfilled” is cheating. No. You’ve simply made a new contract and the new contract is that under certain cases an error is returned in the form of an exception.

> Saying throw an exception and bubble up to main provides just about zero value. Might as well just call std::abort. Which is also something I would never do.

I'm sorry but this is where you're clearly wrong. The whole point is unwinding doesn't have to necessarily happen all the way to main(); there is a ton of value in doing this, and it is not at all equivalent to aborting. It lets someone in the call chain do something other than abort, or clean up stuff that they otherwise might not have a chance to. Like logging an error, telling the client there was an internal error, dumping additional information that wouldn't be useful in the successful case, and/or moving on to another task. All gracefully, without any intermediate functions having to care (aside from providing basic exception safety), and without having to throw your hands up and give up. Aborting without being asked is rather presumptuous and robs your callers of all opportunities to do anything about the problem you encountered.

People do this stuff and find it useful... you're effectively telling them all that they're doing something useless and they may as well just abort. That's... quite a claim.

>If the API is that a function is infallible and then I decide that it’s a fallible function then that’s a pretty major change and I’m just gonna have to update all the call sites to deal with a fallible return result.

What if you don't control those call sites?

>Might as well just call std::abort.

Sure. I mean, not really, because the caller cannot handle an abort. You're making a decision for the caller that the situation is unresolvable, where the caller might disagree.

>No. You’ve simply made a new contract and the new contract is that under certain cases an error is returned in the form of an exception.

If the function doesn't use exceptions for normal error conditions, then no, it's not a new contract, because you don't need to do or know anything specific to handle the situation. You could do something like

  void transaction(){
    try{
      //...
      commit();
    }catch (std::exception &){
      rollback();
    }
  }
and not have to worry about the specifics. It's just an exception. You don't have to care about what exactly happened, you just care that something that couldn't be resolved happened. When exceptions are misused you see stuff like

  try{
    some_function();
  }catch (SomeErrorConditionSpecificToSomeFunction &){
    //...
  }
Not always, but this does usually mean that the exception is part of the contract of the function. It's a condition that the caller must handle as part of the normal usage of the function. FileNotFound exceptions are quite often a prime example of exception abuse.

Replying to your other comment here:

>They’re fallible functions. Don’t write fallible APIs that require exceptions to report errors! That’s bad API design!

I disagree. You should ensure your arguments are valid before indexing vectors and dereferencing optionals. You wouldn't iterate a vector like this, I imagine?

  for (size_t i = 0;; i++){
    auto x = vector.at(i);
    if (!x.has_value())
      break;
    //...
  }
> What if you don't control those call sites?

If I am choosing to change the API contract then someone who wants to use the new API has to update. This is not a big deal.

> If the function doesn't use exceptions for normal error conditions, then no, it's not a new contract

I disrespectfully and emphatically disagree. I do not accept your definition of contract.

> You could do something like (try-catch wrapper)

Let me be clear. Having to add a bunch of random fucking try-catch bullshit around every fucking function call is EXACTLY why I hate exceptions and is EXACTLY what I think is bad software design.

If you think a function should return a value or some unspecified exception whose details are irrelevant then that function could return an option with no information loss, or a result with an Error that is ignored.

> You wouldn't iterate a vector like this, I imagine?

I wouldn’t use at(i) for iteration. The only reason for a function like at(i) to exist is because you want it to be fallible.

> One of the problems with exceptions is it’s utterly impossible to know if a given function call can return exceptions and if so what they are.

Could you please explain how exactly you know if a function might abort?

And how you figure out exactly which error codes a function might return if it does return an error?

And why/how your techniques for figuring out the above don't work for exceptions?

> Python is the bloody worst because I never effing know what the hell any damn function can throw or return. It’s so so frustrating.

No, it's pretty possible. Virtually any interesting function you call can throw AttributeError or TypeError, if nothing else, simply by virtue of you passing an object of the wrong type or behavior.

"But I don't mean those particular exceptions! I don't care about them." Well yeah that's kind of the point. If you can pretend that problems you don't know how to handle don't exist, then you can pretend the same for exceptions and errors. You're not supposed to care about the entire universe of possible error conditions; it's not only impossible but also you wouldn't be able to handle all of them anyway. You handle everything you can reasonably handle and then let the rest propagate, not the other way around. Same for error codes and exceptions.

"But the documentation would tell me which error codes I care about!" Well it can do that for exceptions too. If the documentation sucks then bring it up with the API developer not the language developer.

> But I’ll take Rust results over exceptions 10,000% of the time. Not even a question.

Sure, feel free to do that. Or use error codes in C++, whatever you prefer. Not like I'm trying to turn this into a Rust vs. C++ debate.

Functions aborting is not something I’ve ever really had to think about. Exception heavy codebases it’s something I have to ALWAYS think about.

Error codes are pretty bad. Global error code is awful. An error enum is pretty nice.

So here’s the thing. I’ve been a professional C++ programmer for 20 years. Not once have I ever worked in a codebase that used exceptions. It’s fine. Occasionally I use a thirdparty library that does use exceptions and it’s bloody awful.

> Functions aborting is not something I’ve ever really had to think about.

There are certainly a lot of programs for which it matters a whole lot. In a lot of applications you absolutely don't want your program to crash. I'm not even talking about rocket launching or pacemakers or HFT here... I just mean something as simple as a web server or job server. It's the difference between taking down the server (say, getting DoS'd) vs. not.

> So here’s the thing. I’ve been a professional C++ programmer for 20 years. Not once have I ever worked in a codebase that used exceptions. It’s fine.

I work on codebases without exceptions too; it's not just you. "It's fine" in the same sense that working with a messy desk or floor "is fine". One certainly can live with it -- especially when there's no better option available -- but the clutter gets in the way, distracts you, and sometimes leaves you with a little papercut.

> Occasionally I use a thirdparty library that does use exceptions and it’s bloody awful.

You're not wrong -- this can certainly happen -- but I think you've misidentified the problems. There are other reasons for this than what you've listed in [1] or that I have the energy to fully expand on here, but the biggest one is probably the fact that you're getting the worst of both worlds by the mere virtue of mixing them. Nobody claimed that mixing code that uses exceptions with code that assumes they're disabled would lead to a good outcome. It neither lets you simplify the problem by assuming exceptions will work like they're supposed to, nor does it let you simplify the problem by assuming exceptions are nonexistent. It's like putting cars and bicycles on the same sections of the road all over the town. It's going to lead to more crashes than if you had just stuck with one or the other, and that's not because cars or bikes inherently suck.

[1] https://news.ycombinator.com/item?id=48522500

I interview C++ developers often, and here in 2026 it seems pretty much everyone is using modern (C++20 and up) language versions.

Maybe the tooling finally caught up.

> it's really hard to imagine why you think you'd have a better take on this than Google.

Do you mean this as an appeal to Google being the home to great talent, or more as an endorsement of the specific guidance provided by this specific style guide, Google or no?

Because if the former, I think I do almost everything better in my context than Google would. It would be hard not to considering the difference in organizational scale.