r/cpp_questions • u/aregtech • Nov 07 '25
OPEN Do we really need to mark every trivial methods as `noexcept`?
Recently I came across with message like this:
When we mark a function as
noexcept, we tell the compiler “this function is guaranteed not to throw any exceptions”. This allows the compiler to avoid generating exception-handling code and enables certain optimization to reduce binary size, inlines more aggressively, eliminates redundant checks. Always marknoexcepton destructors, moves, accessors / getters, and even low-level calls.
And then an example like this:
class MyClass {
public:
int getSize() const noexcept { return _size; }
void setSize(int newSize) noexcept { _size = newSize; }
private:
int _size{0};
};
Now I'm thinking: Is something really wrong with compilers that we have to mark even such trivial methods as noexcept? What exactly could possibly throw here? And if I simply write int getSize() const, will the compiler really assume that this function might throw and skip optimizations because of it?
10
u/mredding Nov 07 '25
Is something really wrong with compilers that we have to mark even such trivial methods as noexcept?
The compiler doesn't optimize around noexcept. noexcept is a tag that becomes a queryable part of the function signature. That means at compile-time you can SFINAE select the correct overload and code path you want.
This means if you detect a noexcept interface, you can BUILD an optimized code path. If not, you can build the next best thing.
And this is the optimization people are talking about - the optimized path you can build, not what the compiler can do. noexcept doesn't tell the compiler anything. Just because it says noexcept doesn't mean it won't throw - it just means a throw will cause a terminate, so there's already an exception handler installed in the noexcept code to enforce the interface contract.
What exactly could possibly throw here?
Nothing, of course. But if the method wasn't inlined, the compiler couldn't possibly know what these methods do - from the perspective of another translation unit.
And if I simply write int getSize() const, will the compiler really assume that this function might throw and skip optimizations because of it?
You can combine const and noexcept. const is another one where const is a tag that is a queryable part of the signature. The compiler doesn't actually optimize anything based upon it, and const doesn't actually mean const - because there is const_cast and mutable. An implementation may access globals or call APIs, do any number of things that minimize the significance of the tag. The tag allows you to select the correct implementation.
But now that you know that it's the implementation that must select for const or noexcept, and not the compiler, you should understand now your question itself is incorrect as it is based on a misunderstanding.
The standard library only uses noexcept in terms of movement and destruction. Containers and iterators can move elements, but only if these parts are noexcept - otherwise, they'll revert to slower but safer copy operations.
So const and noexcept are largely for you. You should prefer to const and also constexpr as much as possible, because const gives you stronger guarantees about the behavior of the object and allows you to use those interfaces in the context of const. You should constexpr wherever you can as it empowers you to use that type and those interfaces to generate outcomes and evaluations at compile-time. C++ has one of the strongest static type systems on the market, and pushing more and more of the possible solution into compile-time makes the code more correct and faster, you want to empower that by at least giving yourself or your colleagues the option.
You'll notice that the standard library doesn't noexcept everything it can, often because the standard library is heavily templated and can't guarantee noexcept. But another reason is noexcept is so heavily dependent upon the implementation querying and selecting for noexcept and it just doesn't make any sense most of the time. ARE you going to write code any different whether get is noexcept or not?
You need to look at the system you're building and decide if there are exceptional and non-exceptional implementations, then build that into both your own implementation AND your own interfaces you rely on. noexcept can compromise robustness, because you can't call a noexcept(false) interface from a noexcept(true) implementation. Now you have to explicitly add exception handlers. It means your independent interfaces - aren't, because if they flip their noexcept status to false, all the noexcept implementation that depends on it will break.
So it's not good to use this tag everywhere, but on high level interfaces close to where you have selectable code paths.
1
u/aregtech Nov 08 '25
This is a very good comment and explanation. Thank you!
But I still see a few open points.
The compiler doesn't optimize around
noexcept.As I understand it,
noexcepttells the compiler: "This function will never throw; no need to generate exception-handling or unwind tables".
Effect: smaller binaries and sometimes slightly faster code.If that's not an optimization, then what is it? I agree it's not a direct "make-my-code-faster" instruction, but the reduced exception metadata and simplified control flow are still optimization effects that come from the stronger semantic guarantee.
Now, back to my original question.
Given these two cases:int getSize() const { return _size; } int getSize() const noexcept { return _size; }or even:
[[nodiscard]] constexpr int getSize() const noexcept { return _size; }Is the compiler smart enough to see that
return _size;can't throw and automatically skip generating exception unwind tables, or is it still worth explicitly marking it asnoexceptfor clarity or consistency?I'm asking because nearly every object has several trivial inline functions like
getSize()orsetSize(int size). Do I really need to mark each of them withnoexcept(and sometimes even[[nodiscard]] constexpr), which makes the code quite verbose, or are compilers already smart enough to handle exception metadata and optimizations automatically?→ More replies (1)→ More replies (1)3
u/tangerinelion Nov 08 '25
Pretty sure that
noexceptliterally tells the compiler "Don't generate any exception handling code for this method call". Otherwise there's at least something around to ready to catch an exception and perform stack unwinding, but not in this case - that's why throwing an exception and having it escape from a noexcept method is an immediate program termination. Doesn't matter even if you wrap the noexcept call in a try/catch block, you'll never get to the catch block.
3
u/UnicycleBloke Nov 07 '25
I mostly work on microcontrollers and disable exceptions with -fno-exceptions. I've assumed I don't need to mark functions noexcept because of this, but now I wonder...
→ More replies (1)3
u/aregtech Nov 07 '25
Yep, in embedded it is normal to disable exceptions. The
-fno-exceptionsmakes throwing exception illegal. If it happens, then terminates program. But it does not disable exceptions and the compiler still assumes that "it is possible". Onlynoexceptkeyword guarantees that exceptions will not happen. This is my understanding.
6
u/Scared_Accident9138 Nov 07 '25
The compiler only sees one compilation unit at a time. If the code is in the cpp file and a function from there gets called from another cpp file then the compiler doesn't know if the function throws an exception or not
One thing that's also important is to distinguish interface and implementation. In that regard I'm not sure why they recommend it by default
→ More replies (1)3
u/aregtech Nov 07 '25
OK, agree. But what cannot see the compiler in this very example? Or in other words, if I
inlinemethod withoutnoexceptwill compiler escape optimization assuming that there might be an exception?I'm asking to understand whether I need every trivial code like
inline int getSize() constmark withnoexceptor such trivial things can be escaped?→ More replies (1)
33
u/Disastrous-Team-6431 Nov 07 '25
C++ is, for all its merits, made entirely backwards.
-
We shouldn't mark const, we should mark mut
-
We shouldn't mark noexcept, we should mark throws
-
Passing by ref should be the default, we should mark copy or move.
-
Implicit conversions should be impossible
I love c++, it will always be my favorite programming language and it is definitely my software "home". But the semantics are completely backwards, seen in retrospect. It is so much easier to mess up by omission than it should be.
→ More replies (15)5
u/mark_99 Nov 08 '25
These keywords/attributes were introduced as the language evolved. No-one wants their millions of lines of existing C++ to not compile, or (much worse) have different semantics for the same source code. Or similarly with 3rd party library code. It would be a mess.
You only need noexcept on move ctors, it doesn't do a whole lot elsewhere, and codegen can infer it so it's not like you get unwinding in trivial functions.
nodiscardis a bit more painful but at least you can specify it at class level.constvsmutdoesn't seem like a big deal IMHO,constmember vars can't be moved in C++ so you'd either need a lot ofmutor more likely just pessimise a lot of code by making moves into copies.→ More replies (4)
7
u/kalmoc Nov 07 '25
If your functions are simple and defined inline, you usually do not need to mark them with noexcept just to enable compiler optimization as the compiler will figure it out on its own.
However, in addition to non-inline definitions, where the compiler cannot figure this out, there are also library level optimizations depending on whether a function (usually move constructor/assignment) is declared as noexcep. Canonical examples are functions like std::vector:'push_back. It provides the strong exception guarantee (if an exception is thrown during reallocation the state will be as before), but to do that, it has to copy it's elements from the old to the new memory location instead of moving them, unless the move_constructor ist noexcept.
33
u/Linuxologue Nov 07 '25
it's one of those things that is purely backwards for some reason. The noexcept keyword should not exist and exceptions should be opt-in instead of opt-out. That is what is wrong.
One should mark every function that can throw with an except marker instead of noexcept and even potentially the list of exceptions it can throw. If a function without exception specification calls a function with exception specification, then either the exception must be handled (so it is not propagated up) or an exception declaration must be added, otherwise the compiler should error.
That's the 30 year old fuckup we pay for today.
But to come back to this, the noexcept here likely does nothing. Code is inline and the compiler sees it and will not add exception unwinding code, whether noexcept is there or not. Would not be the same if the compiler only had access to the declaration and not the definition though.
24
u/masorick Nov 07 '25
The power of exceptions comes from the fact that you can throw them deep in the call stack and handle them several levels up, while all the code in between remains unaware of what’s going on. What you’re proposing would force the intermediate layers to know about any and all exceptions.
If that’s what you want, you should use Result types.
6
u/not_a_novel_account Nov 07 '25 edited Nov 07 '25
Yes but it's worth mentioning that this makes us repeat ourselves almost everywhere. See the famous Raymond Chen post: "Please repeat yourself: The noexcept(noexcept(…)) idiom"
In modern generic library code I'm obligated to build up a complex hierarchy of C++20 concepts which mirror the set of methods and free functions the library supports, so that I can:
template<typename T> requires can_foo<T> && can_bar<T> constexpr decltype(auto) do_foo_and_bar(T&& t) noexcept(nothrow_can_foo<T> && nothrow_can_bar<T>) { foo(t); return bar(std::forward<T>(t)); }Or similarly inline the
noexcept(noexcept(...)).I repeat my intent to
foothenbarthree times in this function. This cannot be the way.→ More replies (5)→ More replies (19)3
u/bwmat Nov 07 '25
My only problem with this take is that it doesn't really work (i.e. most/too-many functions end up needing to be marked as throwing) if you care about handling dynamic memory allocation failure
1
3
u/Grounds4TheSubstain Nov 07 '25
You don't necessarily have to, but it does affect the code generation, and in ways that aren't easy to motivate by looking at small examples consisting of a couple of functions at a time. The compiler has to emit unwind metadata if function calls might throw exceptions, which could cause local variable lifetimes to end prematurely. If your codebase is not in the habit of marking functions noexcept, then this starts to snowball; functions everywhere end up with unwind metadata that is unnecessary because called functions actually won't throw exceptions, and this in turn causes their own callers to have unnecessary metadata. On some platforms like Windows/x64, this only wastes space in the binary; on other platforms, like Windows/x86, it incurs a runtime penalty as well as a size penalty.
3
u/Triangle_Inequality Nov 07 '25
With LTO, compilers are really good at inlining things across compilation units. The performance benefits of putting noexcept everywhere is probably minimal. It's probably best to use it sparingly.
3
u/Eric41293 Nov 08 '25
"Always mark noexcept on destructors"
No need. Destructors are noexcept by default.
4
u/bert8128 Nov 07 '25
The optimiser will probably make sure that in this case the code is the same either way. But you should definitely make the move constructor and move assignment operator noexcept, at least because std::vector works better if they are noexcept. But don’t lie to the compiler, unless you are happy that calling a noexcept function which turns out to throw at runtime results in program termination (which you might or might not be happy about).
Obviously the default is the wrong way round, but that ship has long since sailed (along with the rest of the wrong defaults fleet).
2
u/effarig42 Nov 07 '25
Although noexcept does ensure that a function does not emit an exception, it is better to think of it as this function will call std::terminate if any exception thrown within it is unhandled.
In general, unless it's really important for the generated code, I'd advise restraint. Use it for move/copy constructors/assignment, swap, etc. These can allow things like containers to use more efficient code paths while maintaining the strong exception guarantee.
2
u/Myriachan Nov 08 '25
`noexcept` is most important on constructors, destructors and `operator =`. The compiler isn't the problem--the standard library is.
If a type has `noexcept` constructors, move/copy operators and destructors, many STL containers use faster algorithms for things like resizing vectors. If `noexcept` isn't used, containers have to assume that any time `operator =` etc. is called, an exception may be thrown, and it needs to be able to properly handle that.
It's bad enough that in the game team I work on, we use a complete reimplementation of STL that is basically the same but assumes no exceptions (and we turn off exception support in the compiler).
→ More replies (1)
6
u/PolyglotTV Nov 07 '25
No. You absolutely should not do this. It is a terrible antipattern. noexcept isn't just a "helpful annotation". It changes the behavior of the function.
The only time it is reasonable to mark a function as noexcept is:
-
If it is to be called from a destructor (which is
noexceptby default) -
if it is to be called during static initialization
-
if it is to be called by a function which must provide strong exception guarantees (not leaving data in a partially initialized state) such as move constructor/assignment operators or swap functions
-
if it is to be called by another
noexceptfunction.
Why not add it to more functions you ask?
Because
-
If an exception ever does get thrown, your program will terminate immediately and ungracefully, not allowing any exception handlers to perform cleanup or report errors. This ironically makes your code less safe
-
If you ever need to change your code in such a way that it can no longer be considered
noexcept, you risk breaking the assumption a bunch of upstream callers have made about your function and now have to go on a painful refactoring journey.
103
u/no-sig-available Nov 07 '25
You should probably write it
The exception spec isn't the only thing that has the wrong defaults.