using 声明
引入定义于别处的名称到此 using 声明所出现的声明性区域。
using typename (可选) nested-name-specifier unqualified-id ;
|
(C++17 前) | ||||||||
using declarator-list ;
|
(C++17 起) | ||||||||
nested-name-specifier | - | 名称与作用域解析运算符 :: 的序列,以作用域解析运算符结尾。单个 :: 指全局命名空间。
|
unqualified-id | - | id 表达式 |
typename | - | 在 using 声明从基类引入成员类型到类模板时,关键词 typename 可用于解析依赖名所必须处
|
declarator-list | - | 一或多个 typename (可选) nested-name-specifier unqualified-id 的声明器的逗号分隔列表。某些或全部声明器可后随省略号 ... 以指示包展开
|
目录 |
[编辑] 解释
using 声明可用于引入命名空间成员到另一命名空间与块作用域,或引入基类成员到导出类定义。
带多于一个 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 声明引入基类成员到导出类定义,例如暴露基类的受保护成员为导出类的公开成员。此情况下 nested-name-specifier 必须指名被定义的基类。若名称是基类的重载成员函数之名,则引入带该名称的所有基类成员函数。若导出类已有同名、参数列表及限定的成员,则导出类成员隐藏或覆写引入自基类的成员(不与之冲突)。
#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 为受保护 typedef int value_type; }; struct D : B { using B::m; // D::m 为公开 using B::value_type; // D::value_type 为公开 using B::f; void f(int) { std::cout << "D::f\n"; } // D::f(int) 覆写 B::f(int) using B::g; void g(int) { std::cout << "D::g\n"; } // g(int) 与 g(char) 均作为 D 成员可见 using B::h; void h(int) { std::cout << "D::h\n"; } // D::h(int) 隐藏 B::h(int) }; int main() { D d; B& b = d; // b.m = 2; // 错误, B::m 受保护 d.m = 1; // 受保护的 B::m 可作为公开的 D::m 访问 b.f(1); // 调用导出类 f() d.f(1); // 调用导出类 f() d.g(1); // 调用导出类 g(int) d.g('a'); // 调用基类 g(char) b.h(1); // 调用基类 h() d.h(1); // 调用导出类 h() }
输出:
D::f D::f D::g B::g B::h D::h
[编辑] 继承构造函数
若 using 声明指代被定义类的直接基类的构造函数(例如 using Base::Base; ),则继承该基类的构造函数,按照下列规则: 1) 继承构造函数候选的集合由以下组成
a) 基类的所有非模板构造函数(在忽略省略号参数后,若它存在) (C++14 起)
b) 对于每个有默认参数或省略号参数的构造函数,所有构造函数签名由丢弃省略号者,及逐个忽略来自参数列表尾的默认参数者组成
c) 基类的所有构造函数模板(在忽略省略号参数后,若它存在) (C++14 起)
d) 对于每个有默认参数或省略号参数的构造函数模板,所有构造函数签名由丢弃省略号者,及逐个忽略来自参数列表尾的默认参数者组成
2) 所有继承构造函数,之非默认构造函数或复制/移动构造函数,而其签名不匹配用户定义于导出类之构造函数者,为导出类所隐式声明。不继承默认参数:
struct B1 { B1(int); }; struct D1 : B1 { using B1::B1; // 继承构造函数候选集是 // 1. B1(const B1&) // 2. B1(B1&&) // 3. B1(int) // D1 有下列构造函数: // 1. D1() = delete // 2. D1(const D1&) // 3. D1(D1&&) // 4. D1(int) <- 继承的 }; struct B2 { B2(int = 13, int = 42); }; struct D2 : B2 { using B2::B2; // 继承构造函数候选集是 // 1. B2(const B2&) // 2. B2(B2&&) // 3. B2(int = 13, int = 42) // 4. B2(int = 13) // 5. B2() // D2 有下列构造函数: // 1. D2() // 2. D2(const D2&) // 3. D2(D2&&) // 4. D2(int, int) <- 继承的 // 5. D2(int) <- 继承的 }; 继承的构造函数等价于有空函数体和由单个 nested-name-specifier 组成的成员初始化器列表的用户定义构造函数,它转发其所有参数到基类构造函数。 它与对应的基类构造函数有同样的访问。若用户定义构造函数会满足 若 using 声明(从二个有别基类)继承有相同签名的构造函数,则程序为病态。 |
(C++11 起) (C++17 前) |
若 using 声明指代正在定义的类的直接基类(例如 using Base::Base; ),则在初始化导出类时,令该基类的所有构造函数(忽略成员访问)对重载决议可见。 若重载决议选择继承的构造函数,则若它在用于构造对应的积累对象时可访问,则它可访问:忽略引入它的 using 声明的可访问性。 若重载决议在初始化这种导出类对象时选择继承的构造函数之一,则用继承的构造函数初始化从它继承构造函数的 struct B1 { B1(int, ...) { } }; struct B2 { B2(double) { } }; int get(); struct D1 : B1 { using B1::B1; // 继承 B1(int, ...) int x; int y = get(); }; void test() { D1 d(2, 3, 4); // OK : B1 通过调用 B1(2, 3, 4) 初始化, // 然后 d.x 被值初始化(不进行初始化), // 然后 d.y 通过调用 get() 初始化 D1 e; // 错误: D1 无默认构造函数 } struct D2 : B2 { using B2::B2; // 继承 B2(double) B1 b; }; D2 f(1.0); // 错误: B1 无默认构造函数 struct W { W(int); }; struct X : virtual W { using W::W; // 继承 W(int) X() = delete; }; struct Y : X { using X::X; }; struct Z : Y, virtual W { using Y::Y; }; Z z(0); // OK :Y 的初始化不调用 X 的默认构造函数 若从 B 类型的多基类子对象继承构造函数,则程序为病态,类似多继承的非静态成员函数: 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); // 病态:从不同的 B 基类子对象继承的构造函数 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 :只有一个 B 子对象。 // 这初始化虚 B 基类,它初始化 A 基类 // 然后如同用默认化的默认构造函数 // 初始化 V1 与 V2 基类 同任何其他非静态成员函数的 using 声明,若继承的构造函数的签名匹配 struct B1 { B1(int); }; struct B2 { B2(int); }; struct D2 : B1, B2 { using B1::B1; using B2::B2; D2(int); // OK : D2::D2(int) 隐藏 B1::B1(int) 和 B2::B2(int) }; D2 d2(0); // 调用 D2::D2(int) |
(C++17 起) |
[编辑] 注意
唯有显式提及于 using 声明的名称被传送到声明性作用域:特别是, using 声明枚举类型名时,不传送枚举项。
using 声明不能指代命名空间、有作用域枚举项、基类析构函数或用于定义转换函数的成员模板特化。
using 声明可指代成员模板,但不能指代成员模板特化(不容许模板 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 消歧义符)
template<class X> struct B { template<class T> void f(); }; template<class Y> struct D : B<Y> { // using B<Y>::template f; // 错误:不允许消歧义符 using B<Y>::f; // 编译,但声明 f 为非类型非模板 void g() { f<int>(); } // 此处错误 };
若 using 声明带基类赋值运算符到导出类,而其签名恰好匹配导出类的复制赋值或移动赋值运算符,则该运算符为导出类的隐式声明复制/移动赋值运算符所隐藏。同样应用于继承基类的恰好匹配导出类移动/复制构造函数的构造函数的 using 声明 (C++17 起)
using 声明中的包展开令组成的类暴露变长基类的重载成员,而无需递归: template <typename... Ts> struct Overloader : Ts... { using Ts::operator()...; // 从每个基类暴露 operator() }; template <typename... T> Overloader(T...) -> Overloader<T...>; // C++17 推导指引 int main() { auto o = Overloader{ [] (auto const& a) {std::cout << a;}, [] (float f) {std::cout << std::setprecision(3) << f;} }; } |
(C++17 起) |