前言

在 C++ 中,初学者可能经常有一个疑惑:using 到底是干嘛的?using 的功能总有一种覆盖面广的感觉,似乎并没有特定的功能。其实 using 的功能主要可以总结为两个 “引入”、一个 “别名”。其中两个引入是指 using namespace `namespace` using `namespace`::`member` ,一个别名是指 using `alias` = `typename` 。但实际上又并非这么简单,using 在类中又被赋予了更多的意义,本文将就上面讲到的这些内容作整理总结。


using namespace `namespace`

在 C++ using 的三种用法当中,属 using namespace 的用法最纯粹、简单。

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int main() {
cout << "Hello World!" << endl;
// std::cout << "Hello World!" << endl;
return 0;
}

在上面这个示例中,我们使用 using namespace 将命名空间 std 中内容全部引入到当前命名空间(根空间)中,这里命名空间 std 是标准库的空间,所有 C++ 的标准方法都在这个空间中。

因为 std 这个命名空间在当前源文件很常用,所以我们将其全部引入,否则我们每次必须使用第 6 行的内容替代第 5 行,显得非常麻烦。当然,使用 using namespace 也会带来一些命名空间污染的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <utility>
using namespace std;

void move(int &&x) {
cout << "Move: " << x << endl;
}

int main() {
move(10); // 调用 void move(int &&x);
std::move(10); // 调用 std::move
return 0;
}

在这个示例中 move(10) 会调用本源文件的 move,而这也许会和我们期望的结果不同。当然,索性的是,我们依然可以使用 std::move 显式调用。在这里命名空间污染是十分危险的,因为你不知道你导入的包中是否引入其他命名空间,总之尽量不要使用 using namespace

关于 std::move

std::move 用于将左值转换为右值,用于提供移动语义。根据 C++ Primer 介绍,始终建议使用 std::move 而非 move,始终不要引入 std::move 到本地命名空间,正是出于命名空间可能存在污染的原因。


using `namespace`::`member`

引入一般作用域

using namespace 类似,using namespace::member 提供了将其他作用域引入当前作用域的操作。不同的是 using namesapce::member 提供了更加细腻的操作,用于应对 using namespace 存在命名空间污染的问题。当然,同样地,using namespace::member 也存在命名空间污染问题,但其操作更细腻可控。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

int main() {
cout << "Hello World!" << endl;
// std::cout << "Hello World!" << endl;
return 0;
}

在上面这个示例中,我们显式的引入了 std::cin / std::cout / std::endl,而不会引入 std 命名空间中的其他成员,这相对 using namespace std; 更加安全,例如就不存在 std::move 污染的问题。

C++ 17 更新

从 C++ 17 开始,允许使用 , 分隔,同时引入多个命名空间成员。

1
using std::cin, std::cout, std::endl;

引入继承体系(类域)

在继承体系中,using namesapce::memberusing classscope::member)被赋予了更多意义,它可以改变成员在派生类中的访问权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base {
public:
int foo() { return 0; }

protected:
int foo(int x) { return x; }
};

class Derived : public Base {
public:
using Base::foo; // Case 1
double foo(double x) { return x + 0.1; }

protected:
// using Base::foo; // Case 2
};

根据继承体系,在类 Derived 中,int foo(); 的访问权限是 public 的,而 int foo(int); 的访问权限是 protected 的。而使用 using 则可以改变其访问权限,例如 Case 1foo 的两个重载都指定为 public 的,而 Case 2 则将 foo 的两个重载都指定为 protected 的。

需要注意的一点是,如果不使用 using,则新增的 double foo(double) 函数将覆盖原来的两个函数,此时不会发生重载;如果使用 using,则新增的函数将成为 foo 的第三个重载。这一点的表现和一般作用域实际上相同。

引入构造函数

using classscope::member 的体系可谓烦杂,在引入构造函数时(C++ 11 新增),其相对引入继承体系具有新的特殊性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
Base(int) {}

protected:
Base(double) {}

private:
Base(char) {}
};

class Derived : public Base {
public:
using Base::Base;
};
  • 只能引入直接基类的构造函数,例如 class DD : public Derived { using Base::Base; } 是非法的。
  • 引入构造函数不会改变构造函数的访问权限,也就是说 Base::Base 使用在哪个权限下都是一样的,而且构造函数也是默认不继承的(可以使用 using 显式引入),这一点与友元类似。
  • 引入构造函数不会引入其特殊成员函数,包括:默认构造函数,拷贝构造函数、移动构造函数。这些特殊成员函数使用其自己的合成方式自动合成。

using `alias` = `typename`

using alias = typename 与前两者都不同,它用于给类型提供别名(C++ 11 新增)。

一般类型别名

作为类型别名,它与 typedef 类似。

1
2
3
4
5
6
7
8
9
10
11
using i8 = signed char;
using i16 = short;
using i32 = int;
using i64 = long long;
using i128 = __int128_t;

using u8 = unsigned char;
using u16 = unsigned short;
using u32 = unsigned int;
using u64 = unsigned long long;
using u128 = __uint128_t;

类中同样可以使用类型别名,只是它存在访问权限的限制。在类中,它被称为类型成员,是静态成员。

模板类型别名

在类型别名中也支持模板,例如下面的示例。

1
2
3
4
5
#include <vector>
#include <queue>

template <typename T>
using MinHeap = std::priority_queue<T, std::vector<T>, std::greater<T>>;