Lors de la réunion 2016 sur les normes ISO C++ d'Oulu, une proposition intitulée Inline Variables a été adoptée en C++ 17 par le comité de normalisation.
En termes simples, en quoi consistent les variables en ligne, comment fonctionnent-elles et à quoi servent-elles? Comment les variables en ligne doivent-elles être déclarées, définies et utilisées?
La première phrase de la proposition:
” Le spécificateur
inline
peut être appliqué aux variables ainsi qu'aux fonctions.
L’effet ¹ garanti de inline
appliqué à une fonction est de permettre à la fonction d’être définie de manière identique, avec une liaison externe, en plusieurs unités de traduction. En pratique, cela signifie que vous définissez la fonction dans un en-tête, vous pouvez l'inclure dans plusieurs unités de traduction. La proposition étend cette possibilité aux variables.
Ainsi, dans la pratique, la proposition (désormais acceptée) vous permet d’utiliser le mot clé inline
pour définir une liaison externe const
variable de la portée de l’espace de nommage ou tout membre de données de la classe static
, dans un en-tête. fichier, de sorte que les définitions multiples résultant lorsque cet en-tête est inclus dans plusieurs unités de traduction conviennent à l'éditeur de liens - il en choisit simplement un .
Jusqu'en C++ 14 inclus, la machinerie interne était là pour prendre en charge les variables static
dans les modèles de classe, mais il n'existait aucun moyen pratique d'utiliser cette machinerie. Il fallait recourir à des astuces comme
template< class Dummy >
struct Kath_
{
static std::string const hi;
};
template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";
using Kath = Kath_<void>; // Allows you to write `Kath::hi`.
À partir de C++ 17 et après, je pense que l’on peut écrire simplement
struct Kath
{
static std::string const hi;
};
inline std::string const Kath::hi = "Zzzzz..."; // Simpler!
… Dans un fichier d'en-tête.
La proposition comprend la formulation
” Un membre de données statique en ligne peut être défini dans la définition de classe et peut spécifier un initialisateur entre accolades ou égales. Si le membre est déclaré avec le spécificateur
constexpr
, il peut être redéclaré dans la portée de l'espace de noms sans initialiseur (cet usage est déconseillé; voir section D.X). Les déclarations d’autres membres de données statiques ne doivent pas spécifier un identificateur de type accolade ou égal
… Ce qui permet de simplifier davantage ce qui précède
struct Kath
{
static inline std::string const hi = "Zzzzz..."; // Simplest!
};
… Comme noté par T.C dans n commentaire à cette réponse.
De plus, le spécificateur constexpr
implique inline
pour les membres de données statiques ainsi que pour les fonctions.
Remarques:
¹ Pour une fonction inline
a également un effet suggestif sur l'optimisation, le compilateur préférant remplacer les appels de cette fonction par une substitution directe du code machine de la fonction. Cette allusion peut être ignorée.
Les variables en ligne sont très similaires aux fonctions en ligne. Il signale à l'éditeur de liens qu'il ne doit exister qu'une seule instance de la variable, même si la variable est vue dans plusieurs unités de compilation. L'éditeur de liens doit s'assurer qu'aucune autre copie n'est créée.
Les variables en ligne peuvent être utilisées pour définir des éléments globaux dans des bibliothèques contenant uniquement des en-têtes. Avant C++ 17, ils devaient utiliser des solutions de contournement (fonctions inline ou modèles de hacks).
Par exemple, une solution de contournement consiste à utiliser le singleton de Meyer avec une fonction inline:
inline T& instance()
{
static T global;
return global;
}
Cette approche présente certains inconvénients, principalement en termes de performances. Cet encombrement pourrait être évité par des solutions de modèle, mais il est facile de se tromper.
Avec les variables en ligne, vous pouvez le déclarer directement (sans générer d'erreur de l'éditeur de liens à définitions multiples):
inline T global;
En dehors des bibliothèques contenant uniquement des en-têtes, il existe d'autres cas où les variables en ligne peuvent aider. Nir Friedman aborde ce sujet dans son exposé à la CppCon: Ce que les développeurs C++ devraient savoir sur les globals (et l’éditeur de liens)) . La partie sur les variables en ligne et les solutions de contournement commence à 18m9s .
En résumé, si vous devez déclarer des variables globales partagées entre des unités de compilation, leur déclaration en tant que variables en ligne dans le fichier d'en-tête est simple et évite les problèmes liés aux solutions de contournement antérieures à C++ 17.
(Il existe encore des cas d'utilisation du singleton de Meyer, par exemple, si vous souhaitez explicitement avoir une initialisation lente.)
Exemple minimal exécutable
Cette fonctionnalité géniale de C++ 17 nous permet de:
constexpr
: Comment déclarer constexpr extern?main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
notmain.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_i;
}
Compiler et exécuter:
g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main
Voir aussi: Comment fonctionnent les variables en ligne?
Norme C++ sur les variables en ligne
La norme C++ garantit que les adresses seront les mêmes. Projet de norme C++ 17 N4659 10.1.6 "Le spécificateur en ligne":
6 Une fonction ou une variable en ligne avec une liaison externe doit avoir la même adresse dans toutes les unités de traduction.
cppreference https://en.cppreference.com/w/cpp/language/inline explique que si static
n'est pas renseigné, il est doté d'un lien externe.
Implémentation de la variable en ligne GCC
Nous pouvons observer comment cela est implémenté avec:
nm main.o notmain.o
qui contient:
main.o:
U _GLOBAL_OFFSET_TABLE_
U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i
notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i
et man nm
dit à propos de u
:
"u" Le symbole est un symbole global unique. Il s’agit d’une extension GNU de l’ensemble standard de liaisons de symboles ELF. Pour un tel symbole, l’éditeur de liens dynamique s’assurera que dans tout le processus, il n’ya qu’un symbole portant ce nom et ce type en cours d’utilisation.
nous voyons donc qu’il existe une extension ELF dédiée à cela.
Pré-C++ 17: extern const
Avant C++ 17 et en C, nous pouvons obtenir un effet très similaire avec un extern const
, ce qui conduira à l’utilisation d’un seul emplacement mémoire.
Les inconvénients par rapport à inline
sont les suivants:
constexpr
, seul inline
permet que: Comment déclarer constexpr extern?main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
En-tête pré-C++ 17 uniquement alternatives
Celles-ci ne sont pas aussi bonnes que la solution extern
, mais elles fonctionnent et n'occupent qu'un seul emplacement de mémoire:
Une fonction constexpr
, car constexpr
IMPLIQUE inline
et inline
autorise (force) la définition à apparaître sur chaque unité de traduction :
constexpr int shared_inline_constexpr() { return 42; }
et je parie que tout compilateur décent s'inscrira dans l'appel.
Vous pouvez également utiliser une variable statique const
ou constexpr
comme dans:
#include <iostream>
struct MyClass {
static constexpr int i = 42;
};
int main() {
std::cout << MyClass::i << std::endl;
// undefined reference to `MyClass::i'
//std::cout << &MyClass::i << std::endl;
}
mais vous ne pouvez pas prendre son adresse, ou bien il devient odr-used, voir aussi: https://en.cppreference.com/w/cpp/language/static "Membres statiques constants" et Définition des membres de données statiques constexpr
C
En C, la situation est identique à celle de C++ pré C++ 17, un exemple a été téléchargé à l'adresse suivante: Que signifie "statique" en C?
La seule différence est qu'en C++, const
implique static
pour les globals, mais pas en C: Sémantique C++ de `static const` vs` const`
Y at-il un moyen de l’intégrer complètement?
TODO: y a-t-il un moyen de complètement intégrer la variable, sans utiliser de mémoire du tout?
Cela ressemble beaucoup à ce que fait le pré-processeur.
Cela nécessiterait en quelque sorte:
Apparenté, relié, connexe:
Testé sous Ubuntu 18.10, GCC 8.2.0.