ENIAC
ENIAC

Reputation: 1040

Proper Usage of `noexcept`

I'm trying to figure out the proper way of using the noexcept attribute. I've already spent half a day trying to understand it, but I'm not sure I did. And I don't want to use or not to use noexcept everywhere mindlessly.

Question: Do I understand it right, that if a function calls only functions that have noexcept attribute, then we may (or should) safely mark our function as noexcept too?


Suppose, I have functions that check whether a provided path exists and is a directory. I use filesystem for that purpose. One function (IsDir) calls exists and is_directory functions that are not marked as noexcept. The other function (IsDir2) calls exists and is_directory functions that are marked as noexcept. Can I safely mark the second function as noexcept?

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

bool IsDir(const fs::path& p) {
  const bool exists{ fs::exists(p) };        // Not marked as "noexcept".
  const bool is_dir{ fs::is_directory(p) };  // Not marked as "noexcept".
  return exists && is_dir;
}

bool IsDir2(const fs::path& p, std::error_code& ec) noexcept {
  const bool exists{ fs::exists(p, ec) };        // "noexcept" version.
  const bool is_dir{ fs::is_directory(p, ec) };  // "noexcept" version.
  return exists && is_dir;
}

int main () {
  const fs::path p("~/Some_Path");
  std::cout << std::boolalpha << IsDir(p) << std::endl;

  std::error_code ec;
  std::cout << IsDir2(p, ec) << std::endl;

  return 0;
}

Upvotes: 4

Views: 1005

Answers (3)

fabian
fabian

Reputation: 82461

There are 2 scenarios when you'd want to mark a function:

  • To tell the compiler no exception can be thrown from the function
  • To tell the compiler to call std::terminate, if an exception would otherwise "escape" the function.

If only the former is the desired effect, there are several things that you need to take into consideration:

  • There is logic other than functions that could result in exceptions being thrown. Examples are: Calls to operators, conversions(implicit or expicit), constructor calls, ... . Mandatory copy elision can complicate the analysis.
  • Third party functions are not maked noexcept in all scenarios. Some libraries are pre-C++11 compatible and the keyword wasn't even available. Some libraries didn't want to risk ABI breaks or didn't consider it worth the effort adding the keyword in. Some libraries need to be compatible with C, where the keyword isn't available; this includes functions in the C++ standard library such as std::strlen. In some cases where templates are involved, the required conditional noexcept specification could get too complicated for the implementor to bother.

In your IsDir2 your analysis regarding no exception being thrown inside the function body, so marking it as noexcept is appropriate, but as noted by @paxdiablo in the comments the implementation of the function may require an overhaul.

Upvotes: 3

Jan Schultke
Jan Schultke

Reputation: 39623

Lakos Rule - Only functions with wide contracts must be noexcept

The only case in which it is safe to use noexcept is when there's absolutely no way your function will ever throw, even in the future, when the implementation is changed. Your function must have a wide contract, i.e. it has no preconditions at all, and produces a valid result for any possible inputs and global state.

If you mark your function noexcept and it does throw, std::terminate will be called and your program aborts ungracefully. This makes little sense, and it would be better to handle thrown exceptions somewhere at the call site. If need be, put a try ... catch block around all of main. Calling std::terminate prevents any sane error handling and should be avoided.

See also: Arthur O' Dwyer - The Lakos Rule.

Is it okay to break Lakos' Rule?

Yes, the rule is meant for standard library design, and you don't have to be as strict. However, if you put noexcept on a function with a narrow contract, you have to be absolutely sure this will never change, and you will never throw. The code which uses this function may rely on your function being noexcept, and removing noexcept later might break it.

Only put noexcept on functions with narrow contracts when you're sure that they will stay noexcept forever.

For example, std::strcmp is noexcept in some standard libraries because it's a function from C, so it makes no sense for it to throw. However, this is not guaranteed by the C++ standard, because it has a narrow contract (it would break when given a nullptr).

Q & A

Do I understand it right, that if a function calls only functions that have noexcept attribute, then we may (or should) safely mark our function as noexcept too?

It is still possible that our function throws, or might throw in the future. For example:

int div(int x, int y) { // don't make this noexcept, narrow contract
    return x / y; // possible division by zero
}

This function calls no other functions, and it doesn't throw anything, so at first sight, it should be noexcept. However, it has a narrow contract because it is not well-defined for y == 0. Even if it doesn't throw exception now, you might change your mind in the future and throw std::invalid_argument.

bool IsDir2(const fs::path& p, std::error_code& ec) noexcept {
 const bool exists{ fs::exists(p, ec) };        // "noexcept" version.
 const bool is_dir{ fs::is_directory(p, ec) };  // "noexcept" version.
 return exists && is_dir;
}

[IsDir2] calls exists and is_directory functions that are marked as noexcept. Can I safely mark the second function as noexcept?

Yes, but not because it only calls noexcept functions. You mark it noexcept becaues it has a wide contract. If anything fails inside the function, it should communicate that by writing to ec. It would make no sense for it to also throw exceptions, no matter how you implement it.

Upvotes: 2

Jarod42
Jarod42

Reputation: 217265

Functions marked as noexcept behave mostly as

void Function(/*...*/) /* noexcept */
{
   try {
      // ...
   } catch (...) {
       std::terminate();
   }
}

Question: Do I understand it right, that if a function calls only functions that have noexcept attribute, then we may (or should) safely mark our function as noexcept too?

You miss some cases:

  • You might throw yourself, even if only noexcept functions are called.
  • you might catch exception of throwing exception yourself, so your function can promise to not throw

noexcept is part of the interface or your function, so do you want that restriction of not (changing interface might be harder/problematic with retro-compatibility)...

And I don't want to use or not to use noexcept everywhere mindlessly.

noexcept might allow some (micro) optimisations compiler side.

Library side, marking move constructor as noexcept allows std::vector to use move constructor instead of copy constructor in some scenario.

Upvotes: 1

Related Questions