Inline-Namespaces sind eine Bibliotheksversionierungsfunktion, die der Symbolversionierung ähnelt , jedoch nur auf C ++ 11-Ebene (dh plattformübergreifend) implementiert wird, anstatt eine Funktion eines bestimmten ausführbaren Binärformats (dh plattformspezifisch) zu sein.
Es ist ein Mechanismus, mit dem ein Bibliotheksautor einen verschachtelten Namespace so aussehen lassen kann, als ob alle seine Deklarationen im umgebenden Namespace wären (Inline-Namespaces können verschachtelt werden, sodass "mehr verschachtelte" Namen bis zum ersten Nicht-Namespace versickern -inline-Namespace und sehen aus und tun so, als ob ihre Deklarationen auch in einem der dazwischen liegenden Namespaces wären).
Betrachten Sie als Beispiel die STL-Implementierung von vector
. Wenn wir seit Beginn von C ++ Inline-Namespaces <vector>
hätten, hätte der Header in C ++ 98 möglicherweise so ausgesehen:
namespace std {
#if __cplusplus < 1997L // pre-standard C++
inline
#endif
namespace pre_cxx_1997 {
template <class T> __vector_impl; // implementation class
template <class T> // e.g. w/o allocator argument
class vector : __vector_impl<T> { // private inheritance
// ...
};
}
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
# if __cplusplus == 1997L // C++98/03
inline
# endif
namespace cxx_1997 {
// std::vector now has an allocator argument
template <class T, class Alloc=std::allocator<T> >
class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
// ...
};
// and vector<bool> is special:
template <class Alloc=std::allocator<bool> >
class vector<bool> {
// ...
};
};
#endif // C++98/03 or later
} // namespace std
Abhängig vom Wert von __cplusplus
wird entweder die eine oder die andere vector
Implementierung ausgewählt. Wenn Ihre Codebasis 98-mal vor C ++ geschrieben wurde und Sie feststellen, dass die C ++ 98-Version von vector
Ihnen beim Upgrade Ihres Compilers Probleme bereitet, müssen Sie nur die Verweise auf std::vector
in finden Ihre Codebasis und ersetzen Sie sie durch std::pre_cxx_1997::vector
.
Kommen Sie zum nächsten Standard, und der STL-Anbieter wiederholt den Vorgang einfach erneut, indem er einen neuen Namespace std::vector
mit emplace_back
Unterstützung (für den C ++ 11 erforderlich ist) einführt und diesen iff einfügt __cplusplus == 201103L
.
OK, warum brauche ich dafür eine neue Sprachfunktion? Ich kann schon folgendes tun, um den gleichen Effekt zu erzielen, nein?
namespace std {
namespace pre_cxx_1997 {
// ...
}
#if __cplusplus < 1997L // pre-standard C++
using namespace pre_cxx_1997;
#endif
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
namespace cxx_1997 {
// ...
};
# if __cplusplus == 1997L // C++98/03
using namespace cxx_1997;
# endif
#endif // C++98/03 or later
} // namespace std
Je nach Wert von __cplusplus
bekomme ich entweder die eine oder die andere Implementierung.
Und du wärst fast richtig.
Betrachten Sie den folgenden gültigen C ++ 98-Benutzercode (es war zulässig, Vorlagen, die sich bereits std
in C ++ 98 im Namespace befinden, vollständig zu spezialisieren ):
// I don't trust my STL vendor to do this optimisation, so force these
// specializations myself:
namespace std {
template <>
class vector<MyType> : my_special_vector<MyType> {
// ...
};
template <>
class vector<MyOtherType> : my_special_vector<MyOtherType> {
// ...
};
// ...etc...
} // namespace std
Dies ist ein vollkommen gültiger Code, bei dem der Benutzer seine eigene Implementierung eines Vektors für eine Reihe von Typen bereitstellt, bei denen er anscheinend eine effizientere Implementierung kennt als die, die in (ihrer Kopie) der STL zu finden ist.
Aber : Wenn Sie eine Vorlage spezialisieren, müssen Sie dies in dem Namespace tun, in dem sie deklariert wurde. Der Standard sagt, dass dies vector
im Namespace deklariert ist std
, sodass der Benutzer zu Recht erwartet, den Typ zu spezialisieren.
Dieser Code funktioniert mit einem nicht versionierten Namespace std
oder mit der C ++ 11-Inline-Namespace-Funktion, jedoch nicht mit dem verwendeten Versionierungstrick using namespace <nested>
, da dadurch das Implementierungsdetail angezeigt wird, dass der wahre Namespace, in dem vector
definiert wurde, nicht std
direkt war.
Es gibt andere Lücken, anhand derer Sie den verschachtelten Namespace erkennen können (siehe Kommentare unten), aber Inline-Namespaces schließen sie alle. Und das ist alles. Immens nützlich für die Zukunft, aber AFAIK the Standard schreibt keine Inline-Namespace-Namen für seine eigene Standardbibliothek vor (ich würde mich jedoch gerne als falsch erweisen), sodass es nur für Bibliotheken von Drittanbietern verwendet werden kann, nicht der Standard selbst (es sei denn, die Compiler-Anbieter einigen sich auf ein Namensschema).
using namespace V99;
in Stroustrups Beispiel nicht funktioniert.std::cxx_11
. Nicht jeder Compiler wird immer alle alten Versionen der Standardbibliotheken implementieren, auch wenn es im Moment verlockend ist zu glauben, dass es sehr wenig belastend wäre, vorhandene Implementierungen beim Hinzufügen der neuen im alten zu belassen, da sie tatsächlich alle sind sind sowieso. Ich nehme an, was der Standard sinnvollerweise hätte tun können, ist optional, aber mit einem Standardnamen, falls vorhanden.using namespace A
In einem Namespace B werden durch Namen in Namespace B Namen in Namespace A ausgeblendet, wenn Sie danach suchenB::name
- nicht so bei Inline-Namespaces.)ifdef
s für die vollständige Vektorimplementierung verwenden? Alle Implementierungen würden in einem Namespace sein, aber nur eine von ihnen wird nach der Vorverarbeitung definiertusing
Schlüsselwort) angeben.http://www.stroustrup.com/C++11FAQ.html#inline-namespace (ein Dokument, das von Bjarne Stroustrup geschrieben und verwaltet wird und der Ihrer Meinung nach die meisten Motivationen für die meisten C ++ 11-Funktionen kennen sollte. )
Demnach soll die Versionierung aus Gründen der Abwärtskompatibilität zugelassen werden. Sie definieren mehrere innere Namespaces und erstellen den neuesten
inline
. Oder sowieso die Standardeinstellung für Leute, denen die Versionierung egal ist. Ich nehme an, die neueste Version könnte eine zukünftige oder hochmoderne Version sein, die noch nicht standardmäßig ist.Das gegebene Beispiel ist:
Ich verstehe nicht sofort, warum Sie keinen
using namespace V99;
Namespace eingebenMine
, aber ich muss den Anwendungsfall nicht vollständig verstehen, um Bjarnes Wort dafür auf die Motivation des Komitees zu nehmen.quelle
f(1)
Version aus dem Inline-V99
Namespace aufgerufen ?using namespace Mine;
und derMine
Namespace alles aus dem Inline-Namespace enthältMine::V99
.inline
aus der DateiV99.h
in der Version, die enthältV100.h
.Mine.h
Gleichzeitig können Sie natürlich auch Änderungen vornehmen , um ein zusätzliches Include hinzuzufügen.Mine.h
ist Teil der Bibliothek, nicht Teil des Client-Codes.V100.h
, sie installieren eine Bibliothek namens "Mine". Es gibt 3 Header-Dateien in Version 99 von "Mine" -Mine.h
,V98.h
undV99.h
. Es gibt 4 Header - Dateien in Version 100 von "Mine" -Mine.h
,V98.h
,V99.h
undV100.h
. Die Anordnung der Header-Dateien ist ein Implementierungsdetail, das für Benutzer irrelevant ist. Wenn sie ein Kompatibilitätsproblem entdecken, das bedeutet, dass sie einenMine::V98::f
Teil oder den gesamten Code speziell verwenden müssen, können sie AufrufeMine::V98::f
von altem Code mit Aufrufen vonMine::f
neu geschriebenem Code mischen .Mine
, anstatt sich aufMine::V99
oder spezialisieren zu müssenMine::V98
.Neben all den anderen Antworten.
Inline-Namespace kann verwendet werden, um ABI-Informationen oder die Version der Funktionen in den Symbolen zu codieren. Aus diesem Grund werden sie verwendet, um Abwärts-ABI-Kompatibilität bereitzustellen. Mit Inline-Namespaces können Sie Informationen in den verstümmelten Namen (ABI) einfügen, ohne die API zu ändern, da sie nur den Namen des Linkersymbols betreffen.
Betrachten Sie dieses Beispiel:
Angenommen, Sie schreiben eine Funktion
Foo
, die einen Verweis auf ein Objekt sagtbar
und nichts zurückgibt.Sagen Sie in main.cpp
Wenn Sie Ihren Symbolnamen für diese Datei überprüfen, nachdem Sie sie zu einem Objekt kompiliert haben.
Nun könnte es sein, dass
bar
definiert ist als:bar
Kann sich je nach Build-Typ auf zwei verschiedene Typen / Layouts mit denselben Linkersymbolen beziehen.Um ein solches Verhalten zu verhindern, verpacken wir unsere Struktur
bar
in einen Inline-Namespace, in dem das Linkersymbol je nach Build-Typbar
unterschiedlich ist.Also könnten wir schreiben:
Wenn Sie sich nun die Objektdatei jedes Objekts ansehen, erstellen Sie eine mit Release und eine mit Debug-Flag. Sie werden feststellen, dass Linker-Symbole auch Inline-Namespace-Namen enthalten. In diesem Fall
Beachten Sie das Vorhandensein von
rel
unddbg
in den Symbolnamen.Wenn Sie nun versuchen, das Debuggen mit dem Release-Modus zu verknüpfen oder umgekehrt, wird im Gegensatz zum Laufzeitfehler ein Linker-Fehler angezeigt.
quelle
Ich habe tatsächlich eine andere Verwendung für Inline-Namespaces entdeckt.
Mit Qt erhalten Sie einige zusätzliche, nette Funktionen
Q_ENUM_NS
, die wiederum erfordern, dass der einschließende Namespace ein Metaobjekt hat, mit dem deklariert wirdQ_NAMESPACE
. UmQ_ENUM_NS
jedoch arbeiten zu können, mussQ_NAMESPACE
in derselben Datei ein entsprechender ⁽¹⁾ vorhanden sein. Und es kann nur einen geben, oder Sie erhalten doppelte Definitionsfehler. Dies bedeutet effektiv, dass sich alle Ihre Aufzählungen im selben Header befinden müssen. Yuck.Oder ... Sie können Inline-Namespaces verwenden. Das Ausblenden von Aufzählungen in a führt dazu,
inline namespace
dass die Metaobjekte unterschiedliche verstümmelte Namen haben, während für Benutzer der zusätzliche Namespace nicht vorhanden ist⁽²⁾.Sie sind also nützlich, um Inhalte in mehrere Sub-Namespaces aufzuteilen, die alle wie ein Namespace aussehen , wenn Sie dies aus irgendeinem Grund tun müssen. Dies ähnelt natürlich dem Schreiben
using namespace inner
im äußeren Namespace, jedoch ohne die DRY- Verletzung, den Namen des inneren Namespace zweimal zu schreiben.Es ist tatsächlich schlimmer als das; Es muss sich in derselben Zahnspange befinden.
Es sei denn, Sie versuchen, auf das Metaobjekt zuzugreifen, ohne es vollständig zu qualifizieren, aber das Metaobjekt wird kaum jemals direkt verwendet.
quelle
So die wichtigsten Punkte zusammenfassen,
using namespace v99
undinline namespace
waren nicht die gleiche, war die ehemalige eine Abhilfe zu Version Bibliotheken vor einem dedizierten Schlüsselwort (Inline) wurde in C ++ eingeführt 11 , die die Probleme der Verwendung von festenusing
, während der gleichen Versions Funktionalität. Die Verwendung vonusing namespace
verwendet, um Probleme mit ADL zu verursachen (obwohl ADL nun denusing
Anweisungen zu folgen scheint ) und die Offline-Spezialisierung einer Bibliotheksklasse / -funktion usw. durch den Benutzer würde nicht funktionieren, wenn dies außerhalb des wahren Namespace (dessen Name der Benutzer würde und sollte es nicht wissen, dh der Benutzer müsste B :: abi_v2 :: verwenden und nicht nur B ::, damit die Spezialisierung aufgelöst wird.Dies zeigt eine Warnung zur statischen Analyse an
first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
. Wenn Sie jedoch den Namespace A inline machen, löst der Compiler die Spezialisierung korrekt auf. Mit den C ++ 11-Erweiterungen ist das Problem jedoch behoben.Out-of-Line-Definitionen werden bei Verwendung nicht aufgelöst
using
. Sie müssen in einem verschachtelten / nicht verschachtelten Erweiterungs-Namespace-Block deklariert werden (was bedeutet, dass der Benutzer die ABI-Version erneut kennen muss, wenn er aus irgendeinem Grund die Möglichkeit hat, eine eigene Implementierung einer Funktion bereitzustellen).Das Problem verschwindet, wenn B inline geschaltet wird.
Die anderen Funktionen, die
inline
Namespaces bieten, ermöglichen es dem Bibliotheksschreiber, eine transparente Aktualisierung der Bibliothek bereitzustellen. 1) Ohne den Benutzer zu zwingen, den Code mit dem neuen Namespace-Namen umzugestalten. 2) Verhinderung mangelnder Ausführlichkeit und 3) Abstraktion von API-irrelevanten Details. während 4) die gleiche nützliche Linker-Diagnose und das gleiche Verhalten bieten, die die Verwendung eines Nicht-Inline-Namespace bieten würde. Angenommen, Sie verwenden eine Bibliothek:Der Benutzer kann anrufen,
library::foo
ohne die ABI-Version kennen oder in die Dokumentation aufnehmen zu müssen, die sauberer aussieht. Verwendenlibrary::abiverison129389123::foo
würde schmutzig aussehen.Wenn eine Aktualisierung vorgenommen wird
foo
, dh ein neues Mitglied zur Klasse hinzugefügt wird, wirkt sich dies nicht auf vorhandene Programme auf API-Ebene aus, da sie das Mitglied nicht bereits verwenden UND die Änderung des Inline-Namespace-Namens auf API-Ebene nichts ändert weillibrary::foo
wird noch funktionieren.Bei Programmen, die damit verknüpft sind, ist die Änderung für den Linker jedoch nicht transparent, da der Inline-Namespace-Name wie ein regulärer Namespace in Symbolnamen zerlegt wird. Wenn die Anwendung nicht neu kompiliert wird, sondern mit einer neuen Version der Bibliothek verknüpft ist, wird daher ein Symbol
abi_v1
angezeigt, bei dem der Fehler nicht gefunden wird, anstatt ihn tatsächlich zu verknüpfen, und verursacht zur Laufzeit aufgrund der ABI-Inkompatibilität einen mysteriösen Logikfehler. Das Hinzufügen eines neuen Mitglieds führt aufgrund der Änderung der Typdefinition zu einer ABI-Kompatibilität, auch wenn dies das Programm zur Kompilierungszeit (API-Ebene) nicht beeinflusst.In diesem Szenario:
Wie bei der Verwendung von 2 Nicht-Inline-Namespaces kann eine neue Version der Bibliothek verknüpft werden, ohne dass die Anwendung neu kompiliert werden muss, da
abi_v1
sie in einem der globalen Symbole entstellt wird und die richtige (alte) Typdefinition verwendet. Das Neukompilieren der Anwendung würde jedoch dazu führen, dass die Verweise aufgelöst werdenlibrary::abi_v2
.Die Verwendung
using namespace
ist weniger funktional als die Verwendunginline
(da sich Definitionen außerhalb der Zeile nicht auflösen lassen), bietet jedoch die gleichen 4 Vorteile wie oben. Die eigentliche Frage ist jedoch, warum weiterhin eine Problemumgehung verwendet wird, wenn jetzt ein dediziertes Schlüsselwort dafür vorhanden ist. Es ist besser zu üben, weniger ausführlich (muss 1 Codezeile anstelle von 2 ändern) und macht die Absicht klar.quelle