Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 88f82146 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Homemade exception: update with std::source_location.

parent bbcd5967
Branches
No related tags found
1 merge request!91Modifications on notebooks and hands on in part 7
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Home-made exception class](./HomemadeException.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Why-an-home-made-exception-class?" data-toc-modified-id="Why-an-home-made-exception-class?-1">Why an home-made exception class?</a></span><ul class="toc-item"><li><span><a href="#Clunky-constructors-provided-in-std::exception" data-toc-modified-id="Clunky-constructors-provided-in-std::exception-1.1">Clunky constructors provided in <code>std::exception</code></a></span></li><li><span><a href="#std::string-for-storage" data-toc-modified-id="std::string-for-storage-1.2"><code>std::string</code> for storage</a></span></li><li><span><a href="#Indicating-the-file-and-line" data-toc-modified-id="Indicating-the-file-and-line-1.3">Indicating the file and line</a></span></li></ul></li><li><span><a href="#Implementation-of-my-home-made-class" data-toc-modified-id="Implementation-of-my-home-made-class-2">Implementation of my home-made class</a></span><ul class="toc-item"><li><span><a href="#Exception.hpp" data-toc-modified-id="Exception.hpp-2.1">Exception.hpp</a></span></li><li><span><a href="#Exception.cpp" data-toc-modified-id="Exception.cpp-2.2">Exception.cpp</a></span></li><li><span><a href="#Call-to-use-the-exception-class" data-toc-modified-id="Call-to-use-the-exception-class-2.3">Call to use the exception class</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Why an home-made exception class?
### Clunky constructors provided in `std::exception`
`std::exception` provides two constructors: a copy one and a default one without arguments. The idea is that you should provide your own derived exception class for each exception in your program, for which you would override properly the `what()` virtual method to display the adequate message.
I prefer to provide a simple way to provide on spot exception with a mere constructor, to be able to write something like:
```c++
double x = -3.;
if (x < -2. || x > 5.)
throw HomemadeException("x should be in [-2., 5.]");
```
rather than much more verbosy:
%% Cell type:code id: tags:
``` C++17
#include <exception>
class RangeMinus2PlusFiveException : public std::exception
{
public:
RangeMinus2PlusFiveException() = default;
virtual const char* what() const noexcept override
{
return "x should be in [-2., 5.]";
}
};
```
%% Cell type:code id: tags:
``` C++17
{
double x = -3.;
if (x < -2. || x > 5.)
throw RangeMinus2PlusFiveException();
}
```
%% Cell type:markdown id: tags:
### `std::string` for storage
I'm not comfortable either with the `char*` type for the `what` message, especially if you want to tailor it to provide as many information as possible to the end-user (here for instance providing the value of `x` that triggered the exception is much better).
With `char*`, you might encounter rather easily two opposites issues:
- The memory could leak if not properly deallocated.
- On the contrary, if it was somehow released and the exception `what()` is asked later along the line you might end-up with garbage characters in part of your string, as the memory freed might have been used for something else.
We already saw the patch to avoid this kind of issues: using a container with proper RAII ensured. `std::string`is the most obvious choice for a text message.
So in my code I derive my exceptions from a homemade class which is itself derived from `std::exception`; the main difference is that the storage is done with a `std::string` and the override of the `what()` method reads this `std::string`.
### Indicating the file and line
If your code is huge, knowing the exception itself is not enough, if this exception may be thrown from several locations in your code. To identify where the exception was thrown, I use the `__FILE__` and `__LINE__` macro which gives the file and line where they were found in the code (better alternative `std::source_location` is present in C++ 20 but is unfortunately [not well supported yet](https://en.cppreference.com/w/cpp/compiler_support)).
If your code is huge, knowing the exception itself is not enough, if this exception may be thrown from several locations in your code. To identify where the exception was thrown, I used for a long time the `__FILE__` and `__LINE__` macro which gives the file and line where they were found in the code.
So my constructor looks like:
So my constructor looked like:
````
```c++
HomemadeException(const std::string& msg, const char* invoking_file, int invoking_line);
````
```
It might be a bit wordy but:
* It's safe.
* Print is correct.
* It is easy to figure out where it happened.
A better alternative named `std::source_location` was introduced in C++ 20, but it took some time (circa 2023) to be supported properly by both libc++ and libstdc++ (and even currently using libc++ some features don't exactly work [as printed on the can](https://en.cppreference.com/w/cpp/utility/source_location)...)
So current constructor is now:
```c++
HomemadeException(const std::string& msg, const std::source_location location = std::source_location::current());
```
## Implementation of my home-made class
Here is a transcript of my own exception class, which may be found in [my main project](https://gitlab.inria.fr/MoReFEM/CoreLibrary/MoReFEM/tree/master/Sources/Utilities/Exceptions). Namespace has ben removed (you should add one if you intend to use it in practice):
### Exception.hpp
%% Cell type:code id: tags:
``` C++17
#include <string>
#include <exception>
/*!
* \brief Generic class for MoReFEM exceptions.
*/
class Exception: public std::exception
{
public:
/// \name Special members.
///@{
/*!
* \brief Constructor with simple message.
*
* \param[in] msg Message.
* \param[in] invoking_file File that invoked the function or class; usually __FILE__.
* \param[in] invoking_line File that invoked the function or class; usually __LINE__.
*/
//@}
explicit Exception(const std::string& msg, const char* invoking_file, int invoking_line);
//! Destructor
virtual ~Exception() noexcept override;
//! Copy constructor.
Exception(const Exception&) = default;
//! Move constructor.
Exception(Exception&&) = default;
//! Copy assignment.
Exception& operator=(const Exception&) = default;
// Doesn't work on Xeus-cling: C++ 20 on board!
//! Move assignment.
Exception& operator=(Exception&&) = default;
///@}
#include <exception>
#include <source_location>
#include <string>
//! Display the what message from std::exception.
virtual const char* what() const noexcept override final;
/*!
* \brief Display the raw message (Without file and line).
*
* Might be useful if exception is caught to rewrite a more refined message.
*
* Before introducing this, we could end up with something like:
* \verbatim
* Exception caught: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 114: Ill-defined
* finite element space 1: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 101:
* Domain 1 is not defined!
* \endverbatim
*
* Clearly it is nicer to provide:
* \verbatim
* Exception caught: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 114: Ill-defined
* finite element space 1: Domain 1 is not defined!
* \endverbatim
* \brief Generic class for exceptions used in MoReFEM library
*
* \return Exception error message without information about file and line in which the exception was invoked.
* (https://gitlab.inria.fr/MoReFEM/CoreLibrary/MoReFEM)
*/
const std::string& GetRawMessage() const noexcept;
private:
//! The complete what() message (with the location part)
std::string what_message_;
//! Incomplete message (might be useful if we catch an exception to tailor a more specific message).
std::string raw_message_;
};
class Exception : public std::exception
{
public:
/// \name Special members.
///@{
/*!
* \brief Constructor with simple message.
*
* \param[in] msg Message.
* \param[in] location STL object with relevant information about the calling site.
*/
//@}
explicit Exception(const std::string& msg,
const std::source_location location = std::source_location::current());
//! Destructor
virtual ~Exception() noexcept override;
//! Enable default copy constructor.
Exception(const Exception& rhs) = default;
//! Enable default move constructor.
Exception(Exception&& rhs) = default;
//! Enable default assignment operator.
Exception& operator=(const Exception& rhs) = default;
//! Enable default move assignment operator.
Exception& operator=(Exception&& rhs) = default;
///@}
/*!
* \brief Display the what message from std::exception.
*
* \return The what() message as a char* (which reads the internal std::string so no risk of deallocation
* issue).
*/
virtual const char* what() const noexcept override final;
/*!
* \brief Display the raw message (Without file and line).
*
* Might be useful if exception is caught to rewrite a more refined message.
*
* Before introducing this, we could end up in some cases with something like:
* \verbatim
* Exception caught: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 114: Ill-defined
* finite element space 1: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 101:
* Domain 1 is not defined!
* \endverbatim
*
* Clearly it is nicer to provide:
* \verbatim
* Exception caught: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 114: Ill-defined
* finite element space 1: Domain 1 is not defined!
* \endverbatim
*
* \return Exception error message without information about file and line in which the exception was invoked.
*/
const std::string& GetRawMessage() const noexcept;
private:
//! The complete what() message (with the location part)
std::string what_message_;
//! Incomplete message (might be useful if we catch an exception to tailor a more specific message).
std::string raw_message_;
};
```
%% Cell type:markdown id: tags:
### Exception.cpp
%% Cell type:code id: tags:
``` C++17
// Doesn't work on Xeus-cling: C++ 20 on board!
#include <cstring>
#include <iostream>
#include <sstream>
#include <type_traits>
#include <utility>
// #include "Exception.hpp"
#include "Exception.hpp"
namespace // anonymous
{
//! Call this function to set the message displayed by the exception.
void SetWhatMessage(const std::string& msg, std::string& what_message, const char* invoking_file, int invoking_line)
void SetWhatMessage(const std::string& msg, std::string& what_message, const std::source_location location)
{
std::ostringstream stream;
stream << "Exception found at ";
stream << invoking_file << ", line " << invoking_line << ": ";
stream << msg;
stream << "Exception found in file " << location.file_name() << " line " << location.line();
if (strcmp(location.function_name(), "") == 0) // currently Apple clang returns empty!
stream << " in function " << location.function_name();
stream << ": " << msg;
what_message = stream.str();
}
} // namespace anonymous
} // namespace
Exception::Exception(const std::string& msg, const char* invoking_file, int invoking_line)
: std::exception(),
raw_message_(msg)
Exception::Exception(const std::string& msg, const std::source_location location)
: std::exception(), raw_message_(msg)
{
SetWhatMessage(msg, what_message_, invoking_file, invoking_line);
SetWhatMessage(msg, what_message_, location);
}
Exception::~Exception() noexcept = default;
const char* Exception::what() const noexcept
{
return what_message_.c_str();
}
const std::string& Exception::GetRawMessage() const noexcept
{
return raw_message_;
}
```
%% Cell type:markdown id: tags:
### Call to use the exception class
%% Cell type:code id: tags:
``` C++17
{
throw Exception("Example of exception with home-made class", __FILE__, __LINE__);
throw Exception("Example of exception with home-made class", std::source_location::current());
}
```
%% Cell type:code id: tags:
``` C++17
{
int i = -5;
std::ostringstream oconv;
oconv << i << " should have been even!";
throw Exception(oconv.str(), __FILE__, __LINE__);
throw Exception(oconv.str(), std::source_location::current());
}
```
%% Cell type:markdown id: tags:
If you read [the documentation](https://en.cppreference.com/w/cpp/utility/source_location), we should be able to completely avoid the explicit `std::source_location::current()` call at calling site, according to [CppReference](https://en.cppreference.com/w/cpp/utility/source_location/current):
> If current() is used in a default argument, the return value corresponds to the location of the call to current() at the call site.
That is unfortunately not the behaviour I have observed with libc++ on macOS: the file and line returned is the one from the header file, hence the explicit argument at calling site. Likewise, `function_name()` is empty, hence the test in my `SetWhat()` message.
Nonetheless, we may hope these glitches will be solved at some point, and even with them `std::source_location` is more comfy to use than `__FILE__` and `__LINE__` C macros.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment