Commit 833647bd authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Add a new C++ 17 way to define properly a static data attribute.

parent 55df91e8
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [Static attributes](/notebooks/2-ObjectProgramming/5-static.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="#Static-in-C" data-toc-modified-id="Static-in-C-1">Static in C</a></span></li><li><span><a href="#Static-methods" data-toc-modified-id="Static-methods-2">Static methods</a></span><ul class="toc-item"><li><span><a href="#Nitpick:-ClassName()-is-very-inefficient" data-toc-modified-id="Nitpick:-ClassName()-is-very-inefficient-2.1">Nitpick: <code>ClassName()</code> is very inefficient</a></span></li></ul></li><li><span><a href="#Static-data-attributes---to-avoid...-(see-next-section-to-understand-why!)" data-toc-modified-id="Static-data-attributes---to-avoid...-(see-next-section-to-understand-why!)-3">Static data attributes - to avoid... (see next section to understand why!)</a></span></li><li><span><a href="#Static-order-initialization-fiasco---and-its-fix" data-toc-modified-id="Static-order-initialization-fiasco---and-its-fix-4">Static order initialization fiasco - and its fix</a></span></li></ul></div>
<div class="toc"><ul class="toc-item"><li><span><a href="#Static-in-C" data-toc-modified-id="Static-in-C-1">Static in C</a></span></li><li><span><a href="#Static-methods" data-toc-modified-id="Static-methods-2">Static methods</a></span><ul class="toc-item"><li><span><a href="#Nitpick:-ClassName()-is-very-inefficient" data-toc-modified-id="Nitpick:-ClassName()-is-very-inefficient-2.1">Nitpick: <code>ClassName()</code> is very inefficient</a></span></li></ul></li><li><span><a href="#Static-data-attributes---to-avoid...-(see-next-section-to-understand-why!)" data-toc-modified-id="Static-data-attributes---to-avoid...-(see-next-section-to-understand-why!)-3">Static data attributes - to avoid... (see next section to understand why!)</a></span></li><li><span><a href="#Static-order-initialization-fiasco---and-its-fix" data-toc-modified-id="Static-order-initialization-fiasco---and-its-fix-4">Static order initialization fiasco - and its fix</a></span><ul class="toc-item"><li><span><a href="#Update:" data-toc-modified-id="Update:-4.1">Update:</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Static in C
We haven't dealt yet with the keyword `static`, which exists in C.
As the C++ concept of **static method** uses up the same keyword and is not entirely related (even if both may be intertwined as we shall see in the [last section](/notebooks/2-ObjectProgramming/5-static.ipynb#Static-order-initialization-fiasco---and-its-fix)), we shall first review the C concept before studying the C++ one.
First of all, `static` may be seen as _at compile time_, whereas `dynamic` may be seen as _at runtime_.
A reply to this [StackOverflow question](https://stackoverflow.com/questions/572547/what-does-static-mean-in-c) gives a rather good summary of what `static` means in C:
* Static defined local variables do not lose their value between function calls. In other words they are global variables, but scoped to the local function they are defined in.
* Static global variables are not visible outside of the C file they are defined in.
* Static functions are not visible outside of the C file they are defined in.
Only the first one is really relevant in C++, as for quantities that should be accessible only in the current file C++ provides a concept of his own: the [**unnamed namespace**](/notebooks/6-InRealEnvironment/5-Namespace.ipynb#Unnamed-namespace).
Let's see the remaining case in action:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void FunctionWithStatic()
{
static int n = 0; // This initialisation occurs only at first call
// But `n` is not destroyed when the end bracket is reached and remains available
// in subsequent calls.
std::cout << "The function has been called " << ++n << " times." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
for (int i = 0; i < 3; ++i)
FunctionWithStatic();
}
```
%% Cell type:markdown id: tags:
It might be used for instance if you need to initialize something on the very first call of a function:
````
void FunctionWithStatic()
{
static bool is_first_call = true;
if (is_first_call)
{
// Init stuff here on first call only
...
is_first_call = false;
}
... // code executed at each call
}
````
%% Cell type:markdown id: tags:
## Static methods
%% Cell type:markdown id: tags:
Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a **static** keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances.
Static attributes are following the exact same rules as the standard ones regarding the access status (public or private).
%% Cell type:code id: tags:
``` C++17
#include <string>
struct Class
{
static std::string ClassName();
Class() = default;
};
```
%% Cell type:code id: tags:
``` C++17
std::string Class::ClassName()
{
return "Class";
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << "A static method may be called without any object instantiated: "
<< Class::ClassName() << std::endl;
Class obj;
std::cout << "But any object of the class may access it as if it was a regular method: "
<< obj.ClassName() << std::endl;
}
```
%% Cell type:markdown id: tags:
### Nitpick: `ClassName()` is very inefficient
The method above is highly inefficient: the method reinstantiates a new string each time the method is called!
A better implementation would be to use the `static` we already knew from C:
%% Cell type:code id: tags:
``` C++17
#include <string>
struct Class2
{
static const std::string& ClassName();
Class2() = default;
};
```
%% Cell type:code id: tags:
``` C++17
const std::string& Class2::ClassName()
{
static std::string ret = "Class2"; // on the very first call, the string is allocated
return ret; // on any calls, a reference to this instantiation is returned.
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << "A static method may be called without any object instantiated: "
<< Class2::ClassName() << std::endl;
Class2 obj;
std::cout << "But any object of the class may access it as if it was a regular method: "
<< obj.ClassName() << std::endl;
}
```
%% Cell type:markdown id: tags:
We'll see this way of doing thing will solve an issue with data attributes we'll see immediately.
%% Cell type:markdown id: tags:
## Static data attributes - to avoid... (see next section to understand why!)
**Xeus-Cling Warning:** cling doesn't enable proper initialization of a static data atribute... Please considered the following code, available [@Coliru](https://coliru.stacked-crooked.com/a/f43bcc4548a4f160):
%% Cell type:raw id: tags:
#include <iostream>
#include <vector>
struct Class
{
Class();
~Class();
static int Ninstance_;
};
Class::Class()
{
++Ninstance_;
}
Class::~Class()
{
--Ninstance_;
}
// IMPORTANT: this line must be put in a compiled file!
int Class::Ninstance_ = 0;
void Print()
{
std::cout << "There are " << Class::Ninstance_ << " of class Class." << std::endl;
}
int main(int argc, char** argv)
{
Print();
Class obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance_ << std::endl;
Print();
{
std::vector<Class> vec(5);
Print();
}
Print();
return EXIT_SUCCESS;
}
%% Cell type:markdown id: tags:
## Static order initialization fiasco - and its fix
However there is a possible problem not easy to show: when a program is compiled, there are no guarantee whatsoever about the order in which the source files will be compiled. It is therefore completely possible to use a static data attribute in a file *before* its initial value is actually given in another file. This lead to undefined behaviour... The way to fix it is to use a static method instead:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <vector>
struct Class3
{
Class3();
~Class3();
static int& Ninstance(); // notice the reference and the fact it's now a method
};
```
%% Cell type:code id: tags:
``` C++17
int& Class3::Ninstance()
{
static int ret = 0; // the initial value, please notice the use of C static here!
return ret;
}
```
%% Cell type:code id: tags:
``` C++17
Class3::Class3()
{
Ninstance()++;
}
```
%% Cell type:code id: tags:
``` C++17
Class3::~Class3()
{
Ninstance()--;
}
```
%% Cell type:code id: tags:
``` C++17
void Print3()
{
std::cout << "There are " << Class3::Ninstance() << " of class Class." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Print3();
Class3 obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance() << std::endl;
Print3();
{
Class3* vec = new Class3[5];
Print3();
delete[] vec;
}
Print3();
}
```
%% Cell type:markdown id: tags:
To understand better the possible issue and the fix proposed, you may have a look at:
* Item 26 of \cite{Meyers1995}
* The dedicated item on [Parashift FAQ](https://isocpp.org/wiki/faq/ctors#static-init-order)
%% Cell type:markdown id: tags:
### Update:
C++ 17 actually provides a way to define the value in the header file with the `inline` keyword:
%% Cell type:code id: tags:
``` C++17
struct Class4
{
Class4();
~Class4();
static inline int Ninstance_ = 0;
};
```
%% Cell type:markdown id: tags:
Thanks to this [FluentCpp post](https://www.fluentcpp.com/2019/07/23/how-to-define-a-global-constant-in-cpp/) that gave me the hint!
%% Cell type:markdown id: tags:
# References
(<a id="cit-Meyers1995" href="#call-Meyers1995">Meyers, 1995</a>) Scott Meyers, ``_More Effective C++: 35 New Ways to Improve Your Programs and Designs_'', 1995.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
_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)_
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment