Using-Deklaration
Führt einen Namen, der an anderer Stelle definiert ist, in den deklarierten Bereich ein, in dem diese Using-Deklaration erscheint. Siehe using enum und (seit C++20)using namespace für andere verwandte Deklarationen.
using typename(optional) nested-name-specifier unqualified-id ; |
(bis C++17) | ||||||||
using declarator-list ; |
(seit C++17) | ||||||||
typename
|
- | Das Schlüsselwort typename kann nach Bedarf verwendet werden, um abhängige Namen aufzulösen, wenn die Using-Deklaration einen Member-Typ aus einer Basisklasse in einer Klassenvorlage einführt. |
| nested-name-specifier | - | Eine Sequenz von Namen und Gültigkeitsbereichsauflösungsoperatoren ::, die mit einem Gültigkeitsbereichsauflösungsoperator endet. Ein einzelnes :: bezieht sich auf den globalen Namensraum. |
| unqualified-id | - | Ein id-Ausdruck. |
| declarator-list | - | Eine durch Kommas getrennte Liste von einem oder mehreren Deklaratoren des Typs typename(optional) nested-name-specifier unqualified-id. Einige oder alle Deklaratoren können von einem Ellipsensymbol ... gefolgt werden, um eine Pack-Expansion anzuzeigen. |
Inhalt |
[edit] Erklärung
Using-Deklarationen können verwendet werden, um Namensraummitglieder in andere Namensräume und Blockbereiche einzuführen oder Basisklassenmitglieder in abgeleitete Klassendefinitionen einzuführen, oder um Aufzählungswerte in Namensräume, Block- und Klassenbereiche einzuführen(seit C++20).
|
Eine Using-Deklaration mit mehr als einem Using-Deklarator ist äquivalent zu einer entsprechenden Sequenz von Using-Deklarationen mit einem Using-Deklarator. |
(seit C++17) |
[edit] Im Namensraum- und Blockbereich
Using-Deklarationen führen ein Mitglied eines anderen Namensraums in den aktuellen Namensraum oder Blockbereich ein.
#include <iostream> #include <string> using std::string; int main() { string str = "Example"; using std::cout; cout << str; }
Details finden Sie unter Namensraum.
[edit] In Klassendefinitionen
Eine Using-Deklaration führt ein Mitglied einer Basisklasse in die abgeleitete Klassendefinition ein, z. B. um ein geschütztes Mitglied von `base` als öffentliches Mitglied von `derived` freizulegen. In diesem Fall muss der nested-name-specifier eine Basisklasse der zu definierenden Klasse benennen. Wenn der Name der Name einer überladenen Member-Funktion der Basisklasse ist, werden alle Member-Funktionen der Basisklasse mit diesem Namen eingeführt. Wenn die abgeleitete Klasse bereits ein Mitglied mit demselben Namen, derselben Parameterliste und denselben Qualifizierungen hat, verdeckt das Mitglied der abgeleiteten Klasse das aus der Basisklasse eingeführte Mitglied (ohne Konflikt mit ihm).
#include <iostream> struct B { virtual void f(int) { std::cout << "B::f\n"; } void g(char) { std::cout << "B::g\n"; } void h(int) { std::cout << "B::h\n"; } protected: int m; // B::m is protected typedef int value_type; }; struct D : B { using B::m; // D::m is public using B::value_type; // D::value_type is public using B::f; void f(int) override { std::cout << "D::f\n"; } // D::f(int) overrides B::f(int) using B::g; void g(int) { std::cout << "D::g\n"; } // both g(int) and g(char) are visible using B::h; void h(int) { std::cout << "D::h\n"; } // D::h(int) hides B::h(int) }; int main() { D d; B& b = d; // b.m = 2; // Error: B::m is protected d.m = 1; // protected B::m is accessible as public D::m b.f(1); // calls derived f() d.f(1); // calls derived f() std::cout << "----------\n"; d.g(1); // calls derived g(int) d.g('a'); // calls base g(char), exposed via using B::g; std::cout << "----------\n"; b.h(1); // calls base h() d.h(1); // calls derived h() }
Ausgabe
D::f D::f ---------- D::g B::g ---------- B::h D::h
Erbende KonstruktorenWenn sich die *Using-Deklaration* auf einen Konstruktor einer direkten Basis der zu definierenden Klasse bezieht (z. B. using Base::Base;), werden alle Konstruktoren dieser Basis (unter Nichtbeachtung des Member-Zugriffs) bei der Initialisierung der abgeleiteten Klasse für die Überladungsauflösung sichtbar gemacht. Wenn die Überladungsauflösung einen geerbten Konstruktor auswählt, ist er zugänglich, wenn er zugänglich wäre, wenn er zum Konstruieren eines Objekts der entsprechenden Basisklasse verwendet würde: Die Zugänglichkeit der Using-Deklaration, die ihn eingeführt hat, wird ignoriert. Wenn die Überladungsauflösung einen der geerbten Konstruktoren bei der Initialisierung eines Objekts einer solchen abgeleiteten Klasse auswählt, wird das `Base`-Teilobjekt, von dem der Konstruktor geerbt wurde, mit dem geerbten Konstruktor initialisiert, und alle anderen Basen und Member von `Derived` werden initialisiert, als ob sie durch den standardmäßig deklarierten Standardkonstruktor initialisiert würden (standardmäßige Member-Initialisierer werden verwendet, falls vorhanden, andernfalls erfolgt eine Standardinitialisierung). Die gesamte Initialisierung wird als ein einziger Funktionsaufruf behandelt: Die Initialisierung der Parameter des geerbten Konstruktors erfolgt sequenziell vor der Initialisierung von Basen oder Membern des abgeleiteten Objekts. struct B1 { B1(int, ...) {} }; struct B2 { B2(double) {} }; int get(); struct D1 : B1 { using B1::B1; // inherits B1(int, ...) int x; int y = get(); }; void test() { D1 d(2, 3, 4); // OK: B1 is initialized by calling B1(2, 3, 4), // then d.x is default-initialized (no initialization is performed), // then d.y is initialized by calling get() D1 e; // Error: D1 has no default constructor } struct D2 : B2 { using B2::B2; // inherits B2(double) B1 b; }; D2 f(1.0); // error: B1 has no default constructor struct W { W(int); }; struct X : virtual W { using W::W; // inherits W(int) X() = delete; }; struct Y : X { using X::X; }; struct Z : Y, virtual W { using Y::Y; }; Z z(0); // OK: initialization of Y does not invoke default constructor of X Wenn das `Base`-Basisklassen-Teilobjekt nicht als Teil des `Derived`-Objekts initialisiert werden soll (d. h. `Base` ist eine virtuelle Basisklasse von `Derived` und das `Derived`-Objekt ist nicht das am weitesten abgeleitete Objekt), wird die Ausführung des geerbten Konstruktors, einschließlich der Auswertung von Argumenten, weggelassen. struct V { V() = default; V(int); }; struct Q { Q(); }; struct A : virtual V, Q { using V::V; A() = delete; }; int bar() { return 42; } struct B : A { B() : A(bar()) {} // OK }; struct C : B {}; void foo() { C c; // “bar” is not invoked, because the V subobject // is not initialized as part of B // (the V subobject is initialized as part of C, // because “c” is the most derived object) } Wenn der Konstruktor von mehreren Basisklassen-Teilobjekten vom Typ `Base` geerbt wurde, ist das Programm fehlerhaft, ähnlich wie bei mehrfach geerbten Nicht-Static-Member-Funktionen. struct A { A(int); }; struct B : A { using A::A; }; struct C1 : B { using B::B; }; struct C2 : B { using B::B; }; struct D1 : C1, C2 { using C1::C1; using C2::C2; }; D1 d1(0); // ill-formed: constructor inherited from different B base subobjects struct V1 : virtual B { using B::B; }; struct V2 : virtual B { using B::B; }; struct D2 : V1, V2 { using V1::V1; using V2::V2; }; D2 d2(0); // OK: there is only one B subobject. // This initializes the virtual B base class, // which initializes the A base class // then initializes the V1 and V2 base classes // as if by a defaulted default constructor Wie bei Using-Deklarationen für alle anderen Nicht-Static-Member-Funktionen gilt: Wenn ein geerbter Konstruktor der Signatur eines Konstruktors von `Derived` entspricht, wird er durch die in `Derived` gefundene Version von der Suche verdeckt. Wenn einer der geerbten Konstruktoren von `Base` zufällig die Signatur eines Kopier-/Verschiebekonstruktors von `Derived` hat, verhindert dies nicht die implizite Generierung des Kopier-/Verschiebekonstruktors von `Derived` (der dann die geerbte Version verdeckt, ähnlich wie bei `using operator=`). struct B1 { B1(int); }; struct B2 { B2(int); }; struct D2 : B1, B2 { using B1::B1; using B2::B2; D2(int); // OK: D2::D2(int) hides both B1::B1(int) and B2::B2(int) }; D2 d2(0); // calls D2::D2(int) Innerhalb einer Vorlagenklasse, wenn sich eine Using-Deklaration auf einen abhängigen Namen bezieht, wird sie als Konstruktor betrachtet, wenn der nested-name-specifier einen Endnamen hat, der mit dem unqualified-id übereinstimmt. template<class T> struct A : T { using T::T; // OK, inherits constructors of T }; template<class T, class U> struct B : T, A<U> { using A<U>::A; // OK, inherits constructors of A<U> using T::A; // does not inherit constructor of T // even though T may be a specialization of A<> }; |
(seit C++11) |
Geltungsbereichsbezogene Aufzählungen einführenZusätzlich zu Mitgliedern anderer Namensräume und Mitgliedern von Basisklassen kann eine Using-Deklaration auch Aufzählungswerte von Aufzählungen in Namensraum-, Block- und Klassenbereiche einführen. Eine Using-Deklaration kann auch mit unScope-Aufzählungen verwendet werden. enum class button { up, down }; struct S { using button::up; button b = up; // OK }; using button::down; constexpr button non_up = down; // OK constexpr auto get_button(bool is_up) { using button::up, button::down; return is_up ? up : down; // OK } enum unscoped { val }; using unscoped::val; // OK, though needless |
(seit C++20) |
[edit] Anmerkungen
Nur der explizit in der Using-Deklaration erwähnte Name wird in den Deklarationsbereich übertragen: Insbesondere werden Aufzählungswerte nicht übertragen, wenn der Aufzählungstypsname per Using-Deklaration eingeführt wird.
Eine Using-Deklaration kann sich nicht auf einen Namensraum, auf einen Geltungsbereichs-Aufzählungswert(bis C++20), auf einen Destruktor einer Basisklasse oder auf eine Spezialisierung einer Member-Vorlage für eine benutzerdefinierte Konvertierungsfunktion beziehen.
Eine Using-Deklaration kann kein Member-Vorlagen-Spezialisierung benennen (ein template-id ist durch die Grammatik nicht erlaubt).
struct B { template<class T> void f(); }; struct D : B { using B::f; // OK: names a template // using B::f<int>; // Error: names a template specialization void g() { f<int>(); } };
Eine Using-Deklaration kann auch nicht verwendet werden, um den Namen einer abhängigen Member-Vorlage als *template-name* einzuführen (der `template`-Disambiguator für abhängige Namen ist nicht erlaubt).
template<class X> struct B { template<class T> void f(T); }; template<class Y> struct D : B<Y> { // using B<Y>::template f; // Error: disambiguator not allowed using B<Y>::f; // compiles, but f is not a template-name void g() { // f<int>(0); // Error: f is not known to be a template name, // so < does not start a template argument list f(0); // OK } };
Wenn eine Using-Deklaration den Zuweisungsoperator der Basisklasse in die abgeleitete Klasse bringt, dessen Signatur mit dem Kopier- oder Zuweisungsoperator der abgeleiteten Klasse übereinstimmt, wird dieser Operator durch den implizit deklarierten Kopier-/Zuweisungsoperator der abgeleiteten Klasse verdeckt. Dasselbe gilt für eine Using-Deklaration, die einen Basisklassen-Konstruktor erbt, der zufällig mit dem Kopier-/Konstruktor der abgeleiteten Klasse übereinstimmt(seit C++11).
|
Die Semantik des Erbens von Konstruktoren wurde durch ein Defect Report gegen C++11 rückwirkend geändert. Zuvor führte eine erbende Konstruktordeklaration dazu, dass eine Reihe von synthetisierten Konstruktordeklarationen in die abgeleitete Klasse injiziert wurden, was zu redundanten Argumentkopien/-verschiebungen führte, problematische Wechselwirkungen mit einigen Formen von SFINAE hatte und in einigen Fällen auf wichtigen ABIs nicht implementierbar sein konnte. Ältere Compiler können noch die vorherige Semantik implementieren.
|
(seit C++11) |
|
Pack-Expansionen in Using-Deklarationen ermöglichen die Bildung einer Klasse, die überladene Member von variadischen Basen ohne Rekursion freilegt. template<typename... Ts> struct Overloader : Ts... { using Ts::operator()...; // exposes operator() from every base }; template<typename... T> Overloader(T...) -> Overloader<T...>; // C++17 deduction guide, not needed in C++20 int main() { auto o = Overloader{ [] (auto const& a) {std::cout << a;}, [] (float f) {std::cout << std::setprecision(3) << f;} }; } |
(seit C++17) |
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_inheriting_constructors |
200802L |
(C++11) | Erbende Konstruktoren |
201511L |
(C++17) (DR11) |
Umformulierung von erbenden Konstruktoren | |
__cpp_variadic_using |
201611L |
(C++17) | Pack-Expansionen in `using`-Deklarationen |
[edit] Schlüsselwörter
[edit] Defect Reports
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 258 | C++98 | Eine Nicht-`const`-Member-Funktion einer abgeleiteten Klasse kann eine `const`-Member-Funktion ihrer Basis überschreiben und/oder verdecken |
Überschreiben und Verdecken erfordern außerdem gleiche CV-Qualifizierungen. |
| CWG 1738 | C++11 | Es war unklar, ob es erlaubt ist, explizit zu instanziieren oder zu spezialisieren Spezialisierungen von erbenden Konstruktor-Vorlagen. |
verboten |
| CWG 2504 | C++11 | Das Verhalten von erbenden Konstruktoren von virtuellen Basisklassen war unklar. |
wurde klargestellt |
| P0136R1 | C++11 | Die Deklaration eines erbenden Konstruktors injiziert zusätzliche Konstruktoren in die abgeleitete Klasse |
verursacht Basisklassen-Konstruktoren, die durch Namenssuche gefunden werden. |
- Referenzen
[edit] Referenzen
- C++23 Standard (ISO/IEC 14882:2024)
- 9.9 Die `using`-Deklaration [namespace.udecl]
- C++20 Standard (ISO/IEC 14882:2020)
- 9.9 Die `using`-Deklaration [namespace.udecl]
- C++17 Standard (ISO/IEC 14882:2017)
- 10.3.3 Die `using`-Deklaration [namespace.udecl]
- C++14 Standard (ISO/IEC 14882:2014)
- 7.3.3 Die `using`-Deklaration [namespace.udecl]
- C++11 Standard (ISO/IEC 14882:2011)
- 7.3.3 Die `using`-Deklaration [namespace.udecl]
- C++03-Standard (ISO/IEC 14882:2003)
- 7.3.3 Die `using`-Deklaration [namespace.udecl]
- C++98 Standard (ISO/IEC 14882:1998)
- 7.3.3 Die `using`-Deklaration [namespace.udecl]