Beim ISO C++ Standards Meeting 2016 in Oulu wurde ein Vorschlag genannt Inline-Variablen wurde vom Standards Committee in C++17 gewählt.
Laienhaft ausgedrückt, was sind Inline-Variablen, wie funktionieren sie und wofür sind sie nützlich? Wie sollten Inline-Variablen deklariert, definiert und verwendet werden?
Der erste Satz des Vorschlags:
” Dieinline
specifier kann sowohl auf Variablen als auch auf Funktionen angewendet werden.
Die ¹garantierte Wirkung von inline
wie es auf eine Funktion angewendet wird, soll ermöglichen, dass die Funktion mit externer Verknüpfung in mehreren Übersetzungseinheiten identisch definiert wird. Für die Praxis bedeutet das, die Funktion in einem Header zu definieren, der in mehreren Übersetzungseinheiten enthalten sein kann. Der Vorschlag erweitert diese Möglichkeit auf Variablen.
In der Praxis erlaubt Ihnen der (jetzt akzeptierte) Vorschlag also, die zu verwenden inline
Schlüsselwort, um eine externe Verknüpfung zu definieren const
Namespace-Bereichsvariable oder eine beliebige static
Klassendatenelement in einer Header-Datei, sodass die mehreren Definitionen, die sich ergeben, wenn dieser Header in mehreren Übersetzungseinheiten enthalten ist, für den Linker in Ordnung sind – er wählt einfach aus ein von ihnen.
Bis einschließlich C++14 war die interne Maschinerie dafür da, um zu unterstützen static
Variablen in Klassenvorlagen, aber es gab keine bequeme Möglichkeit, diese Maschinerie zu verwenden. Man musste zu Tricks wie greifen
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`.
Ab C++17 kann man meiner Meinung nach nur noch schreiben
struct Kath
{
static std::string const hi;
};
inline std::string const Kath::hi = "Zzzzz..."; // Simpler!
… in einer Header-Datei.
Der Vorschlag enthält den Wortlaut
” Ein statisches Inline-Datenelement kann in der Klassendefinition definiert werden und einen Klammer-oder-Gleich-Initialisierer angeben. Wenn das Mitglied mit deklariert ist constexpr
Spezifizierer, kann er im Namespace-Bereich ohne Initialisierer neu deklariert werden (diese Verwendung ist veraltet; siehe DX). Deklarationen anderer statischer Datenmember dürfen keinen Klammer-oder-Gleichheits-Initialisierer angeben
… wodurch das Obige weiter vereinfacht werden kann
struct Kath
{
static inline std::string const hi = "Zzzzz..."; // Simplest!
};
… wie von TC in einem Kommentar zu dieser Antwort angemerkt.
Auch der constexpr
Bezeichner impliziert inline
für statische Datenmember sowie Funktionen.
Anmerkungen:
¹ Für eine Funktion inline
hat auch einen Hinweis auf die Optimierung, dass der Compiler es vorziehen sollte, Aufrufe dieser Funktion durch direkte Substitution des Maschinencodes der Funktion zu ersetzen. Dieser Hinweis kann ignoriert werden.
Inline-Variablen sind Inline-Funktionen sehr ähnlich. Es signalisiert dem Linker, dass nur eine Instanz der Variablen existieren sollte, selbst wenn die Variable in mehreren Kompilierungseinheiten gesehen wird. Der Linker muss sicherstellen, dass keine weiteren Kopien erstellt werden.
Inline-Variablen können verwendet werden, um Globals in Header-Only-Bibliotheken zu definieren. Vor C++17 mussten sie Workarounds verwenden (Inline-Funktionen oder Template-Hacks).
Eine Problemumgehung besteht beispielsweise darin, die Meyers Singleton mit einer Inline-Funktion:
inline T& instance()
{
static T global;
return global;
}
Dieser Ansatz hat einige Nachteile, hauptsächlich in Bezug auf die Leistung. Dieser Mehraufwand könnte durch Vorlagenlösungen vermieden werden, aber es ist leicht, sie falsch zu machen.
Mit Inline-Variablen können Sie sie direkt deklarieren (ohne einen Linker-Fehler mit mehreren Definitionen zu erhalten):
inline T global;
Abgesehen von reinen Header-Bibliotheken gibt es andere Fälle, in denen Inline-Variablen hilfreich sein können. Nir Friedman behandelt dieses Thema in seinem Vortrag auf der CppCon: Was C++-Entwickler über Globals (und den Linker) wissen sollten. Der Teil über Inline-Variablen und die Problemumgehungen beginnt bei 18m9s.
Um es kurz zu machen, wenn Sie globale Variablen deklarieren müssen, die von Kompilierungseinheiten gemeinsam genutzt werden, ist das Deklarieren als Inline-Variablen in der Header-Datei unkompliziert und vermeidet die Probleme mit Problemumgehungen vor C++17.
(Es gibt immer noch Anwendungsfälle für Meyers Singleton, beispielsweise wenn Sie explizit eine verzögerte Initialisierung wünschen.)
Minimales lauffähiges Beispiel
Mit dieser großartigen C++17-Funktion können wir:
- Verwenden Sie praktischerweise nur eine einzige Speicheradresse für jede Konstante
- speichern Sie es als
constexpr
: Wie deklariert man constexpr extern?
- tun Sie es in einer einzigen Zeile aus einer Kopfzeile
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;
}
Kompilieren und ausführen:
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
GitHub-Upstream.
Siehe auch: Wie funktionieren Inline-Variablen?
C++-Standard für Inline-Variablen
Der C++-Standard garantiert, dass die Adressen gleich sind. C++17 N4659-Standardentwurf
10.1.6 “Der Inline-Bezeichner”:
6 Eine Inline-Funktion oder -Variable mit externer Verknüpfung muss in allen Übersetzungseinheiten dieselbe Adresse haben.
cpReferenz https://en.cppreference.com/w/cpp/language/inline erklärt das wenn static
nicht gegeben ist, dann hat es eine externe Verlinkung.
GCC-Inline-Variablenimplementierung
Wir können beobachten, wie es implementiert wird mit:
nm main.o notmain.o
was beinhaltet:
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
und man nm
sagt über u
:
“u” Das Symbol ist ein eindeutiges globales Symbol. Dies ist eine GNU-Erweiterung des Standardsatzes von ELF-Symbolbindungen. Für ein solches Symbol stellt der dynamische Linker sicher, dass im gesamten Prozess nur ein Symbol mit diesem Namen und Typ verwendet wird.
Wir sehen also, dass es dafür eine dedizierte ELF-Erweiterung gibt.
Vor C++ 17: extern const
Vor C++ 17 und in C können wir mit an einen sehr ähnlichen Effekt erzielen extern const
was dazu führt, dass ein einzelner Speicherplatz verwendet wird.
Die Nachteile vorbei inline
sind:
- es ist nicht möglich, die Variable zu machen
constexpr
nur mit dieser Technik inline
erlaubt das: Wie deklariert man constexpr extern?
- es ist weniger elegant, da Sie die Variable in der Header- und cpp-Datei separat deklarieren und definieren müssen
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
GitHub-Upstream.
Nur Header-Alternativen vor C++17
Diese sind nicht so gut wie die extern
Lösung, aber sie funktionieren und belegen nur einen einzigen Speicherplatz:
EIN constexpr
funktionieren, weil constexpr
impliziert inline
und inline
erlaubt (erzwingt), dass die Definition auf jeder Übersetzungseinheit erscheint:
constexpr int shared_inline_constexpr() { return 42; }
und ich wette, dass jeder anständige Compiler den Aufruf einbetten wird.
Sie können auch eine verwenden const
oder constexpr
statische Integer-Variable wie in:
#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;
}
aber Sie können nicht seine Adresse nehmen, sonst wird es odr-benutzt, siehe auch: https://en.cppreference.com/w/cpp/language/static “Konstante statische Elemente” und Statische Datenelemente von constexpr definieren
C
In C ist die Situation die gleiche wie in C++ vor C++ 17, ich habe ein Beispiel hochgeladen unter: What does “static” mean in C?
Der einzige Unterschied besteht darin, dass in C++ const
impliziert static
für Globals, aber nicht in C: C++-Semantik von `static const` vs `const`
Gibt es eine Möglichkeit, es vollständig zu inlinen?
TODO: Gibt es eine Möglichkeit, die Variable vollständig zu inlinen, ohne überhaupt Speicher zu verwenden?
Ähnlich wie das, was der Präprozessor tut.
Dies würde irgendwie erfordern:
- Verbieten oder Erkennen, ob die Adresse der Variablen belegt ist
- Fügen Sie diese Informationen zu den ELF-Objektdateien hinzu und lassen Sie sie von LTO optimieren
Verwandt:
- C++11-Enumeration mit Klassenmitgliedern und constexpr-Linkzeitoptimierung
Getestet in Ubuntu 18.10, GCC 8.2.0.
@jotik Ich denke, die äquivalente Operation würde jedes Vorkommen der Variablen durch ihren Wert ersetzen. Normalerweise ist dies nur gültig, wenn die Variable ist
const
.– Melpomen
26. Juni 2016 um 22:17 Uhr
Das ist nicht das einzige, was die
inline
Schlüsselwort does für Funktionen. Dieinline
Wenn es auf Funktionen angewendet wird, hat das Schlüsselwort einen weiteren entscheidenden Effekt, der sich direkt auf Variablen überträgt. Eininline
-Funktion, die vermutlich in einer Header-Datei deklariert ist, führt zur Verbindungszeit nicht zu Fehlern durch doppelte Symbole, selbst wenn der Header abgerufen wird#include
d durch mehrere Übersetzungseinheiten. Dieinline
Das Schlüsselwort hat, wenn es auf Variablen angewendet wird, genau das gleiche Ergebnis. Das Ende.– Sam Varshavchik
26. Juni 2016 um 22:22 Uhr
^ Im Sinne von “jeden Aufruf dieser Funktion durch eine direkte Kopie ihres Codes ersetzen”,
inline
ist nur eine schwache, unverbindliche Aufforderung an den Optimierer. Compilern steht es frei, angeforderte Funktionen nicht einzufügen und/oder diejenigen einzufügen, die Sie nicht kommentiert haben. Vielmehr ist der eigentliche Zweck derinline
Schlüsselwort ist, mehrere Definitionsfehler zu umgehen.– Unterstrich_d
25. Juli 2016 um 7:46 Uhr