名稱空間
變體
操作

using 宣告

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

將定義在其他地方的名字引入到此 using 宣告出現的宣告區域中。參見 using enum 以及 (C++20 起)using namespace 以瞭解其他相關的宣告。

using typename(可選) 巢狀名說明符 非限定 ID ; (C++17 前)
using 宣告符列表 ; (C++17 起)
typename - 當 using 宣告從基類將成員型別引入到類模板中時,可根據需要使用關鍵字 typename 來解析依賴名
巢狀名說明符 - 一串名字和作用域解析運算子 ::,以作用域解析運算子結尾。單個 :: 指全域性名稱空間。
非限定 ID - 一個id-表示式
宣告符列表 - 由逗號分隔的一個或多個 typename(可選) 巢狀名說明符 非限定 ID 宣告符列表。部分或全部宣告符後可以跟省略號 ... 以指示包擴充套件

目錄

[編輯] 解釋

using 宣告可用於將名稱空間成員引入其他名稱空間和塊作用域,或將基類成員引入派生類定義中,或將列舉器引入名稱空間、塊和類作用域(C++20 起)

具有多個 using 宣告符的 using 宣告等價於一個由相應的一個 using 宣告符組成的 using 宣告序列。

(C++17 起)

[編輯] 在名稱空間和塊作用域中

using 宣告將另一個名稱空間的成員引入當前名稱空間或塊作用域。

#include <iostream>
#include <string>
 
using std::string;
 
int main()
{
    string str = "Example";
    using std::cout;
    cout << str;
}

詳見名稱空間

[編輯] 在類定義中

Using 宣告將基類的成員引入派生類定義中,例如將基類的受保護成員公開為派生類的公共成員。在這種情況下,巢狀名說明符必須命名正在定義的類的基類。如果該名稱是基類中過載成員函式的名稱,則所有具有該名稱的基類成員函式都會被引入。如果派生類已經擁有具有相同名稱、引數列表和限定符的成員,則派生類成員會隱藏或覆蓋(不與)從基類引入的成員。

#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()
}

輸出

D::f
D::f
----------
D::g
B::g
----------
B::h
D::h

繼承建構函式

如果 using-declaration 指的是正在定義的類的直接基類的建構函式(例如 using Base::Base;),那麼當初始化派生類時,該基類的所有建構函式(忽略成員訪問)都將對過載決議可見。

如果過載決議選擇了一個繼承的建構函式,那麼當它用於構造相應基類的物件時,它是可訪問的:引入它的 using 宣告的可訪問性被忽略。

如果在初始化這樣的派生類物件時,過載決議選擇了一個繼承的建構函式,那麼繼承該建構函式的 Base 子物件將使用該繼承的建構函式進行初始化,而 Derived 的所有其他基和成員都將像預設建構函式那樣進行初始化(如果提供了預設成員初始化器,則使用它們,否則進行預設初始化)。整個初始化被視為一個單一的函式呼叫:繼承建構函式引數的初始化派生物件的任何基或成員的初始化之前進行

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

如果 `Base` 基類子物件不作為 `Derived` 物件的一部分進行初始化(即,`Base` 是 `Derived` 的虛基類,且 `Derived` 物件不是最派生物件),則會省略對繼承建構函式的呼叫,包括任何引數的求值。

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)
}

如果建構函式是從型別為 Base 的多個基類子物件繼承的,則程式是病態的,類似於多重繼承的非靜態成員函式。

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

與任何其他非靜態成員函式的 using 宣告一樣,如果繼承的建構函式與 Derived 的某個建構函式簽名匹配,則它會被 Derived 中的版本隱藏起來。如果 Base 的某個繼承建構函式恰好具有與 Derived 的複製/移動建構函式匹配的簽名,則它不會阻止 Derived 複製/移動建構函式的隱式生成(然後,它會隱藏繼承的版本,類似於 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)

在一個模板類中,如果一個 using 宣告引用了一個依賴名,則如果 巢狀名說明符 的末尾名稱與 非限定 ID 相同,則它被認為是命名一個建構函式。

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<>
};
(C++11 起)


引入有作用域的列舉器

除了其他名稱空間的成員和基類的成員,using 宣告還可以將列舉的列舉器引入名稱空間、塊和類作用域。

using 宣告也可以與無作用域列舉器一起使用。

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
(C++20 起)

[編輯] 注意

只有在 using 宣告中明確提及的名稱才會被轉移到宣告性作用域:特別地,當列舉型別名稱被 using 宣告時,列舉器不會被轉移。

using 宣告不能引用名稱空間、有作用域的列舉器(C++20 前)、基類的解構函式或使用者定義轉換函式的成員模板特化。

using 宣告不能命名成員模板特化(語法不允許 template-id

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>(); }
};

using 宣告也不能用於將依賴成員模板的名稱作為 template-name 引入(不允許對依賴名使用 template 消歧符)。

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
    }   
};

如果一個 using 宣告將基類賦值運算子引入派生類,且其簽名恰好與派生類的複製賦值或移動賦值運算子匹配,則該運算子會被派生類的隱式宣告的複製/移動賦值運算子隱藏。同樣適用於繼承基類建構函式且恰好與派生類複製/移動建構函式匹配的 using 宣告(C++11 起)

繼承建構函式的語義被針對 C++11 的缺陷報告追溯修改。此前,繼承建構函式宣告會在派生類中注入一組合成的建構函式宣告,這導致冗餘的引數複製/移動,與某些 SFINAE 形式存在問題,並且在某些情況下在主要 ABI 上無法實現。舊編譯器可能仍實現以前的語義。

舊的繼承建構函式語義

如果 using-declaration 指的是正在定義的類的直接基類的建構函式(例如 using Base::Base;),那麼該基類的建構函式將根據以下規則被繼承:

1) 一組候選繼承建構函式由以下組成:
a) 基類的所有非模板建構函式(如果存在省略號引數,則省略)(C++14 起)
b) 對於每個帶有預設引數或省略號引數的建構函式,透過逐個刪除省略號和引數列表末尾的預設引數而形成的所有建構函式簽名。
c) 基類的所有建構函式模板(如果存在省略號引數,則省略)(C++14 起)
d) 對於每個帶有預設引數或省略號的建構函式模板,透過逐個刪除省略號和引數列表末尾的預設引數而形成的所有建構函式簽名。
2) 所有不是預設建構函式或複製/移動建構函式,且其簽名不匹配派生類中使用者定義建構函式的候選繼承建構函式,都會在派生類中隱式宣告。預設引數不被繼承。
struct B1
{
    B1(int);
};
 
struct D1 : B1
{
    using B1::B1;
 
    // The set of candidate inherited constructors is 
    // 1. B1(const B1&)
    // 2. B1(B1&&)
    // 3. B1(int)
 
    // D1 has the following constructors:
    // 1. D1() = delete
    // 2. D1(const D1&) 
    // 3. D1(D1&&)
    // 4. D1(int) <- inherited
};
 
struct B2
{
    B2(int = 13, int = 42);
};
 
struct D2 : B2
{
    using B2::B2;
 
    // The set of candidate inherited constructors is
    // 1. B2(const B2&)
    // 2. B2(B2&&)
    // 3. B2(int = 13, int = 42)
    // 4. B2(int = 13)
    // 5. B2()
 
    // D2 has the following constructors:
    // 1. D2()
    // 2. D2(const D2&)
    // 3. D2(D2&&)
    // 4. D2(int, int) <- inherited
    // 5. D2(int) <- inherited
};

繼承的建構函式等價於使用者定義的建構函式,其擁有一個空函式體和一個由單個 巢狀名說明符 組成的成員初始化列表,該列表將其所有引數轉發給基類建構函式。

它擁有與相應基建構函式相同的訪問許可權。如果使用者定義的建構函式滿足 constexpr 建構函式要求,它就是 constexpr 的。如果相應基建構函式被刪除或預設的預設建構函式將被刪除(除了構造正在繼承其建構函式的基類不算在內),則它被刪除。繼承建構函式不能顯式例項化或顯式特化。

如果兩個 using 宣告繼承了具有相同簽名的建構函式(來自兩個直接基類),則程式是病態的。

繼承建構函式模板不應被顯式例項化顯式特化

(C++11 起)

using 宣告中的包擴充套件使得無需遞迴即可形成一個暴露可變基類過載成員的類。

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;} };
}
(C++17 起)
功能測試宏 標準 特性
__cpp_inheriting_constructors 200802L (C++11) 繼承建構函式
201511L (C++17)
(DR11)
重新措辭繼承建構函式
__cpp_variadic_using 201611L (C++17) using 宣告中的包擴充套件

[編輯] 關鍵詞

using

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 258 C++98 派生類的非 const 成員函式可以
覆蓋和/或隱藏其基類的 const 成員函式
覆蓋和隱藏還需要
cv-限定符相同
CWG 1738 C++11 不清楚是否允許
顯式例項化或顯式特化
繼承建構函式模板的特化
已禁止
CWG 2504 C++11 繼承建構函式的行為
來自虛基類不明確
已明確
P0136R1 C++11 繼承建構函式宣告注入
派生類中的額外建構函式
導致基類建構函式
透過名稱查詢找到
  1. 引用

[編輯] 參考

  • C++23 標準 (ISO/IEC 14882:2024)
  • 9.9 using 宣告 [namespace.udecl]
  • C++20 標準 (ISO/IEC 14882:2020)
  • 9.9 using 宣告 [namespace.udecl]
  • C++17 標準 (ISO/IEC 14882:2017)
  • 10.3.3 using 宣告 [namespace.udecl]
  • C++14 標準 (ISO/IEC 14882:2014)
  • 7.3.3 using 宣告 [namespace.udecl]
  • C++11 標準 (ISO/IEC 14882:2011)
  • 7.3.3 using 宣告 [namespace.udecl]
  • C++03 標準 (ISO/IEC 14882:2003)
  • 7.3.3 using 宣告 [namespace.udecl]
  • C++98 標準 (ISO/IEC 14882:1998)
  • 7.3.3 using 宣告 [namespace.udecl]