Mentions légales du service

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

Minor changes in Appendix.

parent 3222117c
Branches
Tags
1 merge request!43Modifications done while reading again the formation
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Appendix](./0-main.ipynb) # [Getting started in C++](./) - [Appendix](./0-main.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This appendix groups some stuff just mentioned in earlier tutorials but that I have chosen not to speak longly about because the lecture is already long enough as it is and they might be a tiny bit more complex for some of them. This appendix groups some stuff just mentioned in earlier tutorials but that I have chosen not to speak longly about because the lecture is already long enough as it is and they might be a tiny bit more complex for some of them.
* [Breaking circular definition of shared pointers with weak pointers](./WeakPtr.ipynb) explains how to set up properly a circular relationship shared/weak pointers (unfortunately the way to set it up properly is often coyly mentioned but not explained); this is a follow-up of the notebook about [smart pointers](../5-UsefulConceptsAndSTL/6-SmartPointers.ipynb).
* [CRTP](./Crtp.ipynb) is one of my favourite idiom to provide a same functionality to utterly different classes; it was teased in the [notebook](../4-Templates/5-MoreAdvanced.ipynb) about advanced features with templates. * [CRTP](./Crtp.ipynb) is one of my favourite idiom to provide a same functionality to utterly different classes; it was teased in the [notebook](../4-Templates/5-MoreAdvanced.ipynb) about advanced features with templates.
* [Breaking circular definition of shared pointers with weak pointers](./WeakPtr.ipynb) explains how to set up properly a circular relationship shared/weak pointers (unfortunately the way to set it up properly is often coyly mentioned but not explained); this is a follow-up of the notebook about [smart pointers](../5-UsefulConceptsAndSTL/6-SmartPointers.ipynb). An existing CRTP from STL is used there at some point so you should probably consider reading it after the one explaining CRTP, but it is by no means a hard prerequisite.
* [Homemade exceptions](./HomemadeException.ipynb) just provides the instantiation of the class I personally use when I want to raise an exception; it's a direct follow-up of the section that [mentioned it](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#The-exception-class-I-use). * [Homemade exceptions](./HomemadeException.ipynb) just provides the instantiation of the class I personally use when I want to raise an exception; it's a direct follow-up of the section that [mentioned it](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#The-exception-class-I-use).
* [Switch](./Switch.ipynb) is the less important one: it just explains `switch` statement and the syntax caveats you might encounter with them. It was mentioned in the [early notebook](../1-ProceduralProgramming/2-Conditions-and-loops.ipynb#switch-statement) about conditions. * [Switch](./Switch.ipynb) is the less important one: it just explains `switch` statement and the syntax caveats you might encounter with them. It was mentioned in the [early notebook](../1-ProceduralProgramming/2-Conditions-and-loops.ipynb#switch-statement) about conditions.
* [StringView](./StringView.ipynb) explains briefly the whereabouts of `std::string_view` which was introduced in C++ 17 - and which will be expanded to other types in C++ 20. * [StringView](./StringView.ipynb) explains briefly the whereabouts of `std::string_view` which was introduced in C++ 17 - and which will be expanded to other types in C++ 20.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2022_ © _CNRS 2016_ - _Inria 2018-2022_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Home-made exception class](./HomemadeException.ipynb) # [Getting started in C++](./) - [Home-made exception class](./HomemadeException.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <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> <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: %% Cell type:markdown id: tags:
## Why an home-made exception class? ## Why an home-made exception class?
### Clunky constructors provided in `std::exception` ### 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. `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: I prefer to provide a simple way to provide on spot exception with a mere constructor, to be able to write something like:
```` ````
double x = -3.; double x = -3.;
if (x < -2. || x > 5.) if (x < -2. || x > 5.)
throw HomemadeException("x should be in [-2., 5.]"); throw HomemadeException("x should be in [-2., 5.]");
```` ````
rather than much more verbosy: rather than much more verbosy:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <exception> #include <exception>
class RangeMinus2PlusFiveException : public std::exception class RangeMinus2PlusFiveException : public std::exception
{ {
public: public:
RangeMinus2PlusFiveException() = default; RangeMinus2PlusFiveException() = default;
virtual const char* what() const noexcept override virtual const char* what() const noexcept override
{ {
return "x should be in [-2., 5.]"; return "x should be in [-2., 5.]";
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
double x = -3.; double x = -3.;
if (x < -2. || x > 5.) if (x < -2. || x > 5.)
throw RangeMinus2PlusFiveException(); throw RangeMinus2PlusFiveException();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `std::string` for storage ### `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). 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: With `char*`, you might encounter rather easily two opposites issues:
- The memory could leak if not properly deallocated. - 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. - 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. 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`. 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 ### 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 [may appear](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#reflection.src_loc) in C++ 20). 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)).
So my constructor looks like: So my constructor looks like:
```` ````
HomemadeException(const std::string& msg, const char* invoking_file, int invoking_line); HomemadeException(const std::string& msg, const char* invoking_file, int invoking_line);
```` ````
It might be a bit wordy but: It might be a bit wordy but:
* It's safe. * It's safe.
* Print is correct. * Print is correct.
* It is easy to figure out where it happened. * It is easy to figure out where it happened.
## Implementation of my home-made class ## 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): 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 ### Exception.hpp
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
#include <exception> #include <exception>
/*! /*!
* \brief Generic class for MoReFEM exceptions. * \brief Generic class for MoReFEM exceptions.
*/ */
class Exception: public std::exception class Exception: public std::exception
{ {
public: public:
/// \name Special members. /// \name Special members.
///@{ ///@{
/*! /*!
* \brief Constructor with simple message. * \brief Constructor with simple message.
* *
* \param[in] msg Message. * \param[in] msg Message.
* \param[in] invoking_file File that invoked the function or class; usually __FILE__. * \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__. * \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); explicit Exception(const std::string& msg, const char* invoking_file, int invoking_line);
//! Destructor //! Destructor
virtual ~Exception() noexcept override; virtual ~Exception() noexcept override;
//! Copy constructor. //! Copy constructor.
Exception(const Exception&) = default; Exception(const Exception&) = default;
//! Move constructor. //! Move constructor.
Exception(Exception&&) = default; Exception(Exception&&) = default;
//! Copy affectation. //! Copy affectation.
Exception& operator=(const Exception&) = default; Exception& operator=(const Exception&) = default;
//! Move affectation. //! Move affectation.
Exception& operator=(Exception&&) = default; Exception& operator=(Exception&&) = default;
///@} ///@}
//! Display the what message from std::exception. //! Display the what message from std::exception.
virtual const char* what() const noexcept override final; virtual const char* what() const noexcept override final;
/*! /*!
* \brief Display the raw message (Without file and line). * \brief Display the raw message (Without file and line).
* *
* Might be useful if exception is caught to rewrite a more refined message. * Might be useful if exception is caught to rewrite a more refined message.
* *
* Before introducing this, we could end up with something like: * Before introducing this, we could end up with something like:
* \verbatim * \verbatim
* Exception caught: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 114: Ill-defined * 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: * finite element space 1: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 101:
* Domain 1 is not defined! * Domain 1 is not defined!
* \endverbatim * \endverbatim
* *
* Clearly it is nicer to provide: * Clearly it is nicer to provide:
* \verbatim * \verbatim
* Exception caught: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 114: Ill-defined * Exception caught: Exception found at Sources/Model/Internal/InitializeHelper.hxx, line 114: Ill-defined
* finite element space 1: Domain 1 is not defined! * finite element space 1: Domain 1 is not defined!
* \endverbatim * \endverbatim
* *
* \return Exception error message withou information about file and line in which the exception was invoked. * \return Exception error message withou information about file and line in which the exception was invoked.
*/ */
const std::string& GetRawMessage() const noexcept; const std::string& GetRawMessage() const noexcept;
private: private:
//! The complete what() message (with the location part) //! The complete what() message (with the location part)
std::string what_message_; std::string what_message_;
//! Incomplete message (might be useful if we catch an exception to tailor a more specific message). //! Incomplete message (might be useful if we catch an exception to tailor a more specific message).
std::string raw_message_; std::string raw_message_;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Exception.cpp ### Exception.cpp
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <utility> #include <utility>
// #include "Exception.hpp" // #include "Exception.hpp"
namespace // anonymous namespace // anonymous
{ {
//! Call this function to set the message displayed by the exception. //! 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 char* invoking_file, int invoking_line)
{ {
std::ostringstream stream; std::ostringstream stream;
stream << "Exception found at "; stream << "Exception found at ";
stream << invoking_file << ", line " << invoking_line << ": "; stream << invoking_file << ", line " << invoking_line << ": ";
stream << msg; stream << msg;
what_message = stream.str(); what_message = stream.str();
} }
} // namespace anonymous } // namespace anonymous
Exception::Exception(const std::string& msg, const char* invoking_file, int invoking_line) Exception::Exception(const std::string& msg, const char* invoking_file, int invoking_line)
: std::exception(), : std::exception(),
raw_message_(msg) raw_message_(msg)
{ {
SetWhatMessage(msg, what_message_, invoking_file, invoking_line); SetWhatMessage(msg, what_message_, invoking_file, invoking_line);
} }
Exception::~Exception() noexcept = default; Exception::~Exception() noexcept = default;
const char* Exception::what() const noexcept const char* Exception::what() const noexcept
{ {
return what_message_.c_str(); return what_message_.c_str();
} }
const std::string& Exception::GetRawMessage() const noexcept const std::string& Exception::GetRawMessage() const noexcept
{ {
return raw_message_; return raw_message_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Call to use the exception class ### Call to use the exception class
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
throw Exception("Example of exception with home-made class", __FILE__, __LINE__); throw Exception("Example of exception with home-made class", __FILE__, __LINE__);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
int i = -5; int i = -5;
std::ostringstream oconv; std::ostringstream oconv;
oconv << i << " should have been even!"; oconv << i << " should have been even!";
throw Exception(oconv.str(), __FILE__, __LINE__); throw Exception(oconv.str(), __FILE__, __LINE__);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2022_ © _CNRS 2016_ - _Inria 2018-2022_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id:451d5c67-1c82-4f19-9fd4-b7dd49bac7fb tags: %% Cell type:markdown id:451d5c67-1c82-4f19-9fd4-b7dd49bac7fb tags:
# [Getting started in C++](./) - [Stringview](./StringView.ipynb) # [Getting started in C++](./) - [Stringview](./StringView.ipynb)
%% Cell type:markdown id:bd83674b-5f88-4d28-b959-ba315588fa67 tags: %% Cell type:markdown id:bd83674b-5f88-4d28-b959-ba315588fa67 tags:
C++ 17 introduced `std::string_view`, which is basically a sort of viewer over a string (not especially a `std::string`: it works basically as long as it's a chain of contiguous characters, where what is a *character* is defined as first template argument). C++ 17 introduced `std::string_view`, which is basically a sort of viewer over a string (not especially a `std::string`: it works basically as long as it's a chain of contiguous characters, where what is a *character* is defined as first template argument).
Let's roll an example to illustrate how it works: Let's roll an example to illustrate how it works:
%% Cell type:code id:d6cc7d26-80ba-4c0a-a276-56f9d850bbbe tags: %% Cell type:code id:d6cc7d26-80ba-4c0a-a276-56f9d850bbbe tags:
``` C++17 ``` C++17
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <iostream> #include <iostream>
void print(std::string_view content) void Print(std::string_view content)
{ {
std::cout << "Content is '" << content << "'" << std::endl; std::cout << "Content is '" << content << "'" << std::endl;
} }
std::string hello("Hello world from std::string!"); std::string hello("Hello world from std::string!");
print(hello); Print(hello);
print("Hello world!"); Print("Hello world!");
``` ```
%% Cell type:markdown id:e565e634-7abb-403a-af47-b46e7e9936e5 tags: %% Cell type:markdown id:e565e634-7abb-403a-af47-b46e7e9936e5 tags:
Prior to C++ 17, the usual way was to use `const std::string&` as parameter (passing a `std::string` directly would incur copies): Prior to C++ 17, the usual way was to use `const std::string&` as parameter (passing a `std::string` directly would incur copies):
%% Cell type:code id:f1562c99-1b25-4314-9903-1932571fe86d tags: %% Cell type:code id:f1562c99-1b25-4314-9903-1932571fe86d tags:
``` C++17 ``` C++17
#include <string> #include <string>
#include <iostream> #include <iostream>
void print_with_const_ref(const std::string& content) void PrintWithConstRef(const std::string& content)
{ {
std::cout << "Content is '" << content << "'" << std::endl; std::cout << "Content is '" << content << "'" << std::endl;
} }
std::string hello("Hello world from std::string!"); std::string hello("Hello world from std::string!");
print_with_const_ref(hello); PrintWithConstRef(hello);
print_with_const_ref("Hello world!"); PrintWithConstRef("Hello world!");
``` ```
%% Cell type:markdown id:4df1c364-df85-4a4d-a0d3-43218189354d tags: %% Cell type:markdown id:4df1c364-df85-4a4d-a0d3-43218189354d tags:
So what did we gain exactly in the bargain? So what did we gain exactly in the bargain?
If you remember the discussion about *l-values* and *r-values* in the [notebook about move semantics](../5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb), the construct used prior to C++ 17 doesn't necessarily make much sense: in our second call the argument `Hello world!` is clearly a r-value whereas a l-value would be expected given the prototype! If you remember the discussion about *l-values* and *r-values* in the [notebook about move semantics](../5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb), the construct used prior to C++ 17 doesn't necessarily make much sense: in our second call the argument `Hello world!` is clearly a r-value whereas a l-value would be expected given the prototype!
%% Cell type:markdown id:414b36fb-0179-4a0f-a963-2c931c93e12a tags: %% Cell type:markdown id:414b36fb-0179-4a0f-a963-2c931c93e12a tags:
Let's write the same *without* the `const` to convince ourselves: Let's write the same *without* the `const` to convince ourselves:
%% Cell type:code id:b7471040-bffb-4a48-8ea0-2ad1fddbf442 tags: %% Cell type:code id:b7471040-bffb-4a48-8ea0-2ad1fddbf442 tags:
``` C++17 ``` C++17
#include <string> #include <string>
#include <iostream> #include <iostream>
void print_with_ref(std::string& content) void PrintWithRef(std::string& content)
{ {
std::cout << "Content is '" << content << "'" << std::endl; std::cout << "Content is '" << content << "'" << std::endl;
} }
print_with_ref("Hello world!"); // COMPILATION ERROR! PrintWithRef("Hello world!"); // COMPILATION ERROR!
``` ```
%% Cell type:markdown id:f9dc26d5-1b95-4c75-8ca8-901e6962215e tags: %% Cell type:markdown id:f9dc26d5-1b95-4c75-8ca8-901e6962215e tags:
This time it doesn't work at all... This time it doesn't work at all...
So when we provide a r-value argument to a `const std::string&` parameter, the compiler does some magic to interpret it. This magic is not without cost: a `std::string` is allocated on the fly to store the content of the r-value. So when we provide a r-value argument to a `const std::string&` parameter, the compiler does some magic to interpret it. This magic is not without cost: a `std::string` is allocated on the fly to store the content of the r-value.
That is what `std::string_view` strives to correct: when it is used there are no such allocation involved. That is what `std::string_view` strives to correct: when it is used there are no such allocation involved.
So whenever possible, using `std::string_view` instead of `const std::string&` is advised - provided of course your project is firmly grounded in C++ 17 or later. So whenever possible, using `std::string_view` instead of `const std::string&` is advised - provided of course your project is firmly grounded in C++ 17 or later.
That doesn't mean there are no costs involved: when you are using `std::string_view`, you are essentially taking the responsability to ensure the memory area still exists (very similarly to the kind of contract you pass when you define a reference). [CppReference](https://en.cppreference.com/w/cpp/string/basic_string_view) illustrates this on a very basic case: That doesn't mean there are no costs involved: when you are using `std::string_view`, you are essentially taking the responsability to ensure the memory area still exists (very similarly to the kind of contract you pass when you define a reference). [CppReference](https://en.cppreference.com/w/cpp/string/basic_string_view) illustrates this on a very basic case:
%% Cell type:code id:157f9843-02d4-4c7b-b062-f4403f911cd8 tags: %% Cell type:code id:157f9843-02d4-4c7b-b062-f4403f911cd8 tags:
``` C++17 ``` C++17
// Beware: the code in this cell seems to disturb Xeus-cling ability to write to std::cout... // Beware: the code in this cell seems to disturb Xeus-cling ability to write to std::cout...
// A ticket has been emitted: https://github.com/jupyter-xeus/xeus-cling/issues/405 // A ticket has been emitted: https://github.com/jupyter-xeus/xeus-cling/issues/405
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <string_view> #include <string_view>
std::string_view bad(std::string("a temporary string")); // "bad" holds a dangling pointer std::string_view bad(std::string("a temporary string")); // "bad" holds a dangling pointer
// Any use of bad here will result in undefined behaviour as the `std::string` was destroyed in // Any use of bad here will result in undefined behaviour as the `std::string` was destroyed in
// previous line when it went out of scope! // previous line when it went out of scope!
``` ```
%% Cell type:markdown id:d8be6dd3-6ecf-4861-a556-35fe159e3445 tags: %% Cell type:markdown id:d8be6dd3-6ecf-4861-a556-35fe159e3445 tags:
C++ 20 will expand on this possibility for other contiguous containers with [`std::span`](https://en.cppreference.com/w/cpp/container/span). C++ 20 will expand on this possibility for other contiguous containers with [`std::span`](https://en.cppreference.com/w/cpp/container/span).
%% Cell type:markdown id:d46d567d-50e9-4fef-a689-deaaff8d059a tags: %% Cell type:markdown id:d46d567d-50e9-4fef-a689-deaaff8d059a tags:
© _Inria 2021-2022_ © _Inria 2021_
_This notebook is released under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is released under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
......
...@@ -302,5 +302,5 @@ ...@@ -302,5 +302,5 @@
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 2 "nbformat_minor": 4
} }
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Weak pointers](./WeakPtr.ipynb) # [Getting started in C++](./) - [Weak pointers](./WeakPtr.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#How-to-handle-properly-circular-relationship-with-shared_ptr" data-toc-modified-id="How-to-handle-properly-circular-relationship-with-shared_ptr-1">How to handle properly circular relationship with <code>shared_ptr</code></a></span><ul class="toc-item"><li><span><a href="#Illustration-of-circular-definition-problem" data-toc-modified-id="Illustration-of-circular-definition-problem-1.1">Illustration of circular definition problem</a></span></li><li><span><a href="#weak_ptr" data-toc-modified-id="weak_ptr-1.2"><code>weak_ptr</code></a></span></li><li><span><a href="#Proper-usage-of-lock()" data-toc-modified-id="Proper-usage-of-lock()-1.3">Proper usage of <code>lock()</code></a></span></li><li><span><a href="#Design-issue:-make-the-code-hard-to-misuse!" data-toc-modified-id="Design-issue:-make-the-code-hard-to-misuse!-1.4">Design issue: make the code hard to misuse!</a></span></li><li><span><a href="#Design-issue:-make-the-code-hard-to-misuse!" data-toc-modified-id="Design-issue:-make-the-code-hard-to-misuse!-1.5">Design issue: make the code hard to misuse!</a></span></li></ul></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#How-to-handle-properly-circular-relationship-with-shared_ptr" data-toc-modified-id="How-to-handle-properly-circular-relationship-with-shared_ptr-1">How to handle properly circular relationship with <code>shared_ptr</code></a></span><ul class="toc-item"><li><span><a href="#Illustration-of-circular-definition-problem" data-toc-modified-id="Illustration-of-circular-definition-problem-1.1">Illustration of circular definition problem</a></span></li><li><span><a href="#weak_ptr" data-toc-modified-id="weak_ptr-1.2"><code>weak_ptr</code></a></span></li><li><span><a href="#Proper-usage-of-lock()" data-toc-modified-id="Proper-usage-of-lock()-1.3">Proper usage of <code>lock()</code></a></span></li><li><span><a href="#Design-issue:-make-the-code-hard-to-misuse!" data-toc-modified-id="Design-issue:-make-the-code-hard-to-misuse!-1.4">Design issue: make the code hard to misuse!</a></span></li><li><span><a href="#Design-issue:-make-the-code-hard-to-misuse!" data-toc-modified-id="Design-issue:-make-the-code-hard-to-misuse!-1.5">Design issue: make the code hard to misuse!</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## How to handle properly circular relationship with `shared_ptr` ## How to handle properly circular relationship with `shared_ptr`
### Illustration of circular definition problem ### Illustration of circular definition problem
The danger with `shared_ptr` is circular definition: you might end up not releasing properly the memory: The danger with `shared_ptr` is circular definition: you might end up not releasing properly the memory:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <string> #include <string>
// Forward declaration; see dedicated notebook! // Forward declaration; see dedicated notebook!
class Child; class Child;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
(If a refresher is needed, the notebook for [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)). (If a refresher is needed, the notebook for [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Father class Father
{ {
public: public:
Father(std::string name); Father(std::string name);
~Father(); ~Father();
void AddChild(const std::shared_ptr<Child>& child); void AddChild(const std::shared_ptr<Child>& child);
void Print() const; void Print() const;
private: private:
std::string name_; std::string name_;
std::vector<std::shared_ptr<Child>> children_; std::vector<std::shared_ptr<Child>> children_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Child class Child
{ {
public: public:
Child(std::string name, const std::shared_ptr<Father>& father); Child(std::string name, const std::shared_ptr<Father>& father);
~Child(); ~Child();
const std::string& GetName() const; // noexcept unfortunately not supported by Xeus-cling const std::string& GetName() const; // noexcept unfortunately not supported by Xeus-cling
private: private:
std::string name_; std::string name_;
std::shared_ptr<Father> father_ = nullptr; std::shared_ptr<Father> father_ = nullptr;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Father::AddChild(const std::shared_ptr<Child>& child) void Father::AddChild(const std::shared_ptr<Child>& child)
{ {
children_.push_back(child); children_.push_back(child);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Father::Father(std::string name) Father::Father(std::string name)
: name_(name) : name_(name)
{ {
std::cout << "Allocating father " << name_ << std::endl; std::cout << "Allocating father " << name_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Child::Child(std::string name, const std::shared_ptr<Father>& father) Child::Child(std::string name, const std::shared_ptr<Father>& father)
: name_(name), : name_(name),
father_(father) father_(father)
{ {
std::cout << "Allocating child " << name_ << std::endl; std::cout << "Allocating child " << name_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Father::Print() const void Father::Print() const
{ {
std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl; std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl;
for (const auto& child : children_) for (const auto& child : children_)
std::cout<< "\t" << child->GetName() << std::endl; std::cout<< "\t" << child->GetName() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Father::~Father() Father::~Father()
{ {
std::cout << "Release Father " << name_ << std::endl; std::cout << "Release Father " << name_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Child::~Child() Child::~Child()
{ {
std::cout << "Release Child " << name_ << std::endl; std::cout << "Release Child " << name_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
const std::string& Child::GetName() const // noexcept const std::string& Child::GetName() const // noexcept
{ {
return name_; return name_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
auto father = std::make_shared<Father>("Darth Vader"); auto father = std::make_shared<Father>("Darth Vader");
auto boy = std::make_shared<Child>("Luke", father); auto boy = std::make_shared<Child>("Luke", father);
auto girl = std::make_shared<Child>("Leia", father); auto girl = std::make_shared<Child>("Leia", father);
father->AddChild(boy); father->AddChild(boy);
father->AddChild(girl); father->AddChild(girl);
father->Print(); father->Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are two strong issues with this code: There are two strong issues with this code:
* We see destructors aren't properly called: due to circular reference neither counter ever reaches 0, hence preventing the deletion of the underlying objects `Father` and `Child`... * We see destructors aren't properly called: due to circular reference neither counter ever reaches 0, hence preventing the deletion of the underlying objects `Father` and `Child`...
* Moreover, this code is very unsatisfactorily on the design side: if the end-user forgets or does not know `AddChild` must be called, we would have ill-defined objects with a father that doesn't know its child... * Moreover, this code is very unsatisfactorily on the design side: if the end-user forgets or does not know `AddChild` must be called, we would have ill-defined objects with a father that doesn't know its child...
Ideally we would like to add the `Father::AddChild()` call to the `Child` constructor, but we need a way to be able to provide the `shared_ptr` related to the main class in `Child` constructor. Ideally we would like to add the `Father::AddChild()` call to the `Child` constructor, but we need a way to be able to provide the `shared_ptr` related to the main class in `Child` constructor.
### `weak_ptr` ### `weak_ptr`
The first issue is resolved with a `weak_ptr`: it is very similar to a `shared_ptr` except that is doesn't increase the reference count; so the underlying object may be destroyed even if the weak pointer still exists. The first issue is resolved with a `weak_ptr`: it is very similar to a `shared_ptr` except that is doesn't increase the reference count; so the underlying object may be destroyed even if the weak pointer still exists.
It is only used for storage; you can't use the object underneath directly and have to build a `shared_ptr` from it before use with the method `lock()`. It is only used for storage; you can't use the object underneath directly and have to build a `shared_ptr` from it before use with the method `lock()`.
There are just two modifications to do (used to be done here but circa 2021 Xeus emit a link so the code will be put instead [@Coliru](http://coliru.stacked-crooked.com/a/2013674aee9983d5): There are just two modifications to do (used to be done here but circa 2021 Xeus emit a link so the code will be put instead [@Coliru](http://coliru.stacked-crooked.com/a/2013674aee9983d5)):
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
- In `Father` class, use this to store children: - In `Father` class, use this to store children:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::vector<std::weak_ptr<Child2>> children_; std::vector<std::weak_ptr<Child2>> children_;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
- `Print()` becomes (slightly) more complicated: - `Print()` becomes (slightly) more complicated:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Father::Print() const void Father::Print() const
{ {
std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl; std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl;
for (const auto& child : children_) for (const auto& child : children_)
{ {
auto shared = child.lock(); auto shared = child.lock();
std::cout<< "\t" << shared->GetName() << std::endl; std::cout<< "\t" << shared->GetName() << std::endl;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And the memory is now properly released! And the memory is now properly released!
### Proper usage of `lock()` ### Proper usage of `lock()`
However our code is currently not robust: we forget to check whether the underlying objects are still valid. Let's consider the following main: However our code is currently not robust: we forget to check whether the underlying objects are still valid. Let's consider the following main:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
auto father = std::make_shared<Father2>("Darth Vader"); auto father = std::make_shared<Father2>("Darth Vader");
auto boy = std::make_shared<Child2>("Luke", father); auto boy = std::make_shared<Child2>("Luke", father);
auto girl = std::make_shared<Child2>("Leia", father); auto girl = std::make_shared<Child2>("Leia", father);
father->AddChild(boy); father->AddChild(boy);
father->AddChild(girl); father->AddChild(girl);
{ {
auto mistake = std::make_shared<Child2>("Yoda", father); // ages don't even match! auto mistake = std::make_shared<Child2>("Yoda", father); // ages don't even match!
father->AddChild(mistake); father->AddChild(mistake);
} // mistake is released here: it becomes out of scope, and children_ data attribute } // mistake is released here: it becomes out of scope, and children_ data attribute
// is made of weak pointers. // is made of weak pointers.
father->Print(); father->Print();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This code causes an issue (which might be a crash or an error depending on your platform): the vector still includes `Yoda` as a child of `Darth Vader`, whereas the object no longer exists (we're once again in undefined behavior territory). This code causes an issue (which might be a crash or an error depending on your platform): the vector still includes `Yoda` as a child of `Darth Vader`, whereas the object no longer exists (we're once again in undefined behavior territory).
There is a method `expired()` that returns `true` if the underlying object no longer exists; but in fact lock gets a return value that may be used to the same purpose (see [@Coliru](http://coliru.stacked-crooked.com/a/fcdb5cf21897e4ef)): There is a method `expired()` that returns `true` if the underlying object no longer exists; but in fact `lock()` gets a return value that may be used to the same purpose (see [@Coliru](http://coliru.stacked-crooked.com/a/fcdb5cf21897e4ef)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Father::Print() const void Father::Print() const
{ {
std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl; std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl;
for (const auto& child : children_) for (const auto& child : children_)
{ {
if (auto shared = child.lock()) // we just added the condition here! if (auto shared = child.lock()) // we just added the condition here!
std::cout<< "\t" << shared->GetName() << std::endl; std::cout<< "\t" << shared->GetName() << std::endl;
} }
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
auto father = std::make_shared<Father2>("Darth Vader"); auto father = std::make_shared<Father2>("Darth Vader");
auto boy = std::make_shared<Child2>("Luke", father); auto boy = std::make_shared<Child2>("Luke", father);
auto girl = std::make_shared<Child2>("Leia", father); auto girl = std::make_shared<Child2>("Leia", father);
father->AddChild(boy); father->AddChild(boy);
father->AddChild(girl); father->AddChild(girl);
{ {
auto mistake = std::make_shared<Child2>("Yoda", father); // ages don't match! auto mistake = std::make_shared<Child2>("Yoda", father); // ages don't match!
father->AddChild(mistake); father->AddChild(mistake);
} }
father->PrintWithLock(); father->PrintWithLock();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
With this proper use of `lock()`, we at least avoid the undefined behaviour about the objects behind the weak pointers... With this proper use of `lock()`, we at least avoid the undefined behaviour about the objects behind the weak pointers...
But the code is not satisfactorily: we get as result: But the code is not satisfactorily: we get as result:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
```` ````
Allocating father Darth Vader Allocating father Darth Vader
Allocating child Luke Allocating child Luke
Allocating child Leia Allocating child Leia
Allocating child Yoda Allocating child Yoda
Release Child Yoda Release Child Yoda
Darth Vader is the father of 3 children: Darth Vader is the father of 3 children:
Luke Luke
Leia Leia
Release Child Leia Release Child Leia
Release Child Luke Release Child Luke
Release Father Darth Vader Release Father Darth Vader
```` ````
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Design issue: make the code hard to misuse! ### Design issue: make the code hard to misuse!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Now we will tackle the remaining design issues: Now we will tackle the remaining design issues:
- Making the end-user call `AddChild()` himself is very ill-advised, as you put on him the burden of making sure the objects are consistant. - Making the end-user call `AddChild()` himself is very ill-advised, as you put on him the burden of making sure the objects are consistant.
- If a `Child` is removed at some point, it remains present in the `Father` dedicated data attribute `children_`; taking the size of this vector thus yields a wrong result. This second issue is not as bad as the first one: `children_` is a private data attribute that is not exposed publicly at all; it might be acceptable to keep this behaviour and provide a method that yields the number of children (and in this case documenting it properly in Doxygen or equivalent: if someone fiddles with the class implementation in the future he must be fully aware of it... and this someone could be yourself in few months!). - If a `Child` is removed at some point, it remains present in the `Father` dedicated data attribute `children_`; taking the size of this vector thus yields a wrong result. This second issue is not as bad as the first one: `children_` is a private data attribute that is not exposed publicly at all; it might be acceptable to keep this behaviour and provide a method that yields the number of children (and in this case documenting it properly in Doxygen or equivalent: if someone fiddles with the class implementation in the future he must be fully aware of it... and this someone could be yourself in few months!).
What would be the neater way to solve the first issue would be to do this step during the `Child` constructor: we set the relationship both ways at the same time. What we lacked is a way to derive a `shared_ptr` from the `this` pointer; STL provides a CRTP to do exactly that: `std::enable_shared_from_this`. With this CRTP, we gain a `shared_from_this()` method that provides exactly what we need (you should have a look at [cppreference](https://en.cppreference.com/w/cpp/memory/enable_shared_from_this) for more details about why you shouldn't try to create the shared pointer from this yourself). What would be the neater way to solve the first issue would be to do this step during the `Child` constructor: we set the relationship both ways at the same time. What we lacked is a way to derive a `shared_ptr` from the `this` pointer; STL provides a CRTP to do exactly that: `std::enable_shared_from_this`. With this CRTP, we gain a `shared_from_this()` method that provides exactly what we need (you should have a look at [cppreference](https://en.cppreference.com/w/cpp/memory/enable_shared_from_this) for more details about why you shouldn't try to create the shared pointer from this yourself).
Unfortunately, due to this being a CRTP, we can't call it from the constructor (see [the notebook dedicated to CRTP](./Crtp.ipynb#Never-call-a-CRTP-method-in-base-constructor!"); we therefore need to add an `Init()` method. To ensure the end-user doesn't forget it, we may add a variable that checks initialisation has been properly done, at least in debug mode. Unfortunately, due to this being a CRTP, we can't call it from the constructor (see [the notebook dedicated to CRTP](./Crtp.ipynb#Never-call-a-CRTP-method-in-base-constructor!"); we therefore need to add an `Init()` method. To ensure the end-user doesn't forget it, we may add a variable that checks initialisation has been properly done, at least in debug mode.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <string> #include <string>
#include <cassert> #include <cassert>
// Forward declaration // Forward declaration
class Child3; class Child3;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Father3 class Father3
{ {
public: public:
// Friendship: `Child3` will need access to `AddChild` and `UpdateChildren`, // Friendship: `Child3` will need access to `AddChild` and `UpdateChildren`,
// but those methods have no reason otherwise to be publicly available. // but those methods have no reason otherwise to be publicly available.
friend Child3; friend Child3;
Father3(std::string name); Father3(std::string name);
~Father3(); ~Father3();
void Print() const; void Print() const;
private: private:
void AddChild(const std::shared_ptr<Child3>& child); void AddChild(const std::shared_ptr<Child3>& child);
void UpdateChildren(); void UpdateChildren();
std::string name_; std::string name_;
std::vector<std::weak_ptr<Child3>> children_; std::vector<std::weak_ptr<Child3>> children_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Child3 : public std::enable_shared_from_this<Child3> // CRTP! class Child3 : public std::enable_shared_from_this<Child3> // CRTP!
{ {
public: public:
Child3(std::string name, const std::shared_ptr<Father3>& father); Child3(std::string name, const std::shared_ptr<Father3>& father);
~Child3(); ~Child3();
void Init(); void Init();
const std::string& GetName() const; // noexcept would be even better! const std::string& GetName() const; // noexcept would be even better!
// #ifndef NDEBUG - commented for Xeus-cling only... // #ifndef NDEBUG - commented for Xeus-cling only...
bool IsInitialised() const; // noexcept would be even better! bool IsInitialised() const; // noexcept would be even better!
// #endif // NDEBUG - commented for Xeus-cling only... // #endif // NDEBUG - commented for Xeus-cling only...
private: private:
// #ifndef NDEBUG - commented for Xeus-cling only... // #ifndef NDEBUG - commented for Xeus-cling only...
bool is_initialised_ = false; bool is_initialised_ = false;
// #endif // NDEBUG - commented for Xeus-cling only... // #endif // NDEBUG - commented for Xeus-cling only...
std::string name_; std::string name_;
std::shared_ptr<Father3> father_ = nullptr; std::shared_ptr<Father3> father_ = nullptr;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Father3::AddChild(const std::shared_ptr<Child3>& child) void Father3::AddChild(const std::shared_ptr<Child3>& child)
{ {
children_.push_back(child); children_.push_back(child);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Father3::Father3(std::string name) Father3::Father3(std::string name)
: name_(name) : name_(name)
{ {
std::cout << "Allocating father " << name_ << std::endl; std::cout << "Allocating father " << name_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Child3::Child3(std::string name, const std::shared_ptr<Father3>& father) Child3::Child3(std::string name, const std::shared_ptr<Father3>& father)
: name_(name), : name_(name),
father_(father) father_(father)
{ {
std::cout << "Allocating child " << name_ << std::endl; std::cout << "Allocating child " << name_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Child3::Init() void Child3::Init()
{ {
#ifndef NDEBUG #ifndef NDEBUG
is_initialised_ = true; is_initialised_ = true;
#endif // NDEBUG #endif // NDEBUG
father_->AddChild(shared_from_this()); father_->AddChild(shared_from_this());
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
//#ifndef NDEBUG - commented for Xeus-cling only... //#ifndef NDEBUG - commented for Xeus-cling only...
bool Child3::IsInitialised() const // noexcept bool Child3::IsInitialised() const // noexcept
{ {
return is_initialised_; return is_initialised_;
} }
//#endif // NDEBUG - commented for Xeus-cling only... //#endif // NDEBUG - commented for Xeus-cling only...
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Father3::Print() const void Father3::Print() const
{ {
std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl; std::cout << name_ << " is the father of " << children_.size() << " children: " << std::endl;
for (const auto& child : children_) for (const auto& child : children_)
{ {
if (auto shared = child.lock()) if (auto shared = child.lock())
{ {
assert(shared->IsInitialised()); assert(shared->IsInitialised());
std::cout<< "\t" << shared->GetName() << std::endl; std::cout<< "\t" << shared->GetName() << std::endl;
} }
} }
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Father3::UpdateChildren() void Father3::UpdateChildren()
{ {
auto logical_end = std::remove_if(children_.begin(), auto logical_end = std::remove_if(children_.begin(),
children_.end(), children_.end(),
[](auto& weak_ptr) [](auto& weak_ptr)
{ {
return weak_ptr.expired(); return weak_ptr.expired();
}); });
children_.erase(logical_end, children_.end()); children_.erase(logical_end, children_.end());
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you're a bit lost by the implementation above, please check a former [notebook](../5-UsefulConceptsAndSTL/7-Algorithms.ipynb#std::remove). If you're a bit lost by the implementation above, please check a former [notebook](../5-UsefulConceptsAndSTL/7-Algorithms.ipynb#std::remove).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Father3::~Father3() Father3::~Father3()
{ {
std::cout << "Release Father " << name_ << std::endl; std::cout << "Release Father " << name_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Child3::~Child3() Child3::~Child3()
{ {
std::cout << "Release Child " << name_ << std::endl; std::cout << "Release Child " << name_ << std::endl;
try // Remember: no exception should be thrown from destructors! try // Remember: no exception should be thrown from destructors!
// And to be honest even the std::cout should be put inside: // And to be honest even the std::cout should be put inside:
// exception could be thrown during operator<< calls. // exception could be thrown during operator<< calls.
{ {
father_->UpdateChildren(); father_->UpdateChildren();
} }
catch(const std::exception& e) catch(const std::exception& e)
{ {
std::cerr << "An exception occured in Child3 destructor; it is neutralized and " std::cerr << "An exception occured in Child3 destructor; it is neutralized and "
"the program will abort." << std::endl; "the program will abort." << std::endl;
abort(); abort();
} }
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
const std::string& Child3::GetName() const // noexcept const std::string& Child3::GetName() const // noexcept
{ {
return name_; return name_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
auto father = std::make_shared<Father3>("Darth Vader"); auto father = std::make_shared<Father3>("Darth Vader");
auto boy = std::make_shared<Child3>("Luke", father); auto boy = std::make_shared<Child3>("Luke", father);
auto girl = std::make_shared<Child3>("Leia", father); auto girl = std::make_shared<Child3>("Leia", father);
boy->Init(); boy->Init();
girl->Init(); girl->Init();
{ {
auto mistake = std::make_shared<Child3>("Yoda", father); auto mistake = std::make_shared<Child3>("Yoda", father);
mistake->Init(); mistake->Init();
} }
father->Print(); father->Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
With this, we have a perfectly valid and safe circular loop between two classes. With this, we have a perfectly valid and safe circular loop between two classes.
As you can surmise, you shouldn't rely too much on such circular dependancies that are clearly a lot of work to set-up to be full-proof; it is best to think from the beginning in which way your relationship is defined and stick to it as much as possible. As you can surmise, you shouldn't rely too much on such circular dependancies that are clearly a lot of work to set-up to be full-proof; it is best to think from the beginning in which way your relationship is defined and stick to it as much as possible.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2022_ © _CNRS 2016_ - _Inria 2018-2022_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment