在 c++ 的标准库中,因为类继承关系比较复杂和模板使用比较多的原因,源代码中充斥着 typename、typedef 和 using 这三个关键字

typename 关键字

typename 的第一个作用是用作模板里面,来声明某种类型,比如这样的:

1
2
template<typename _Tp, typename _Alloc>
struct _Vector_base;

最开始的时候声明模板形参,也会使用 class,但我们都知道 class 总要是用来指定一个类名,据说是为了避免混淆,所以后来增加了 typename 这个关键字,它告诉编译器,跟在它后面的字符串是一个不确定的类型,而不是变量或者其他什么东西。

typename 在 stl 中还有另外一种作用,显示地告诉编译器一个名字表示的是类型。

1
2
3
4
5
6
7
8
9
//test.cpp
#include <ext/alloc_traits.h>
using namespace std;

template<typename _Tp, typename _Alloc>
class AA
{
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;
};

这里顺便说一下 rebind 前面为啥要放一个 template,它是为了告诉编译器后面的 <> 是用于指定模板参数,而进行比较。

这个时候我们使用g++ -c test.cpp -o test.o是可以编译通过的,但如果我们去掉第三个 typename 看,会发生什么呢?

再次编译,报错如下:

1
2
test.cpp:8:10: 错误:‘typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Tp>::other’之前需要‘typename’,因为‘typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Tp>’是一个有依赖的作用域
typedef __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;

编译器直接指明了需要一个 typename,实际上 typename 在这里也是指定它后面的字符串为类型,这是因为对于形如AA::BB这样的形式,它有可能是一个类型,也有可能是类的静态成员,这个时候加上 typename 就是为了告诉编译器,它后面的一大串字符串都是一个类型。


在阅读一些代码是时常看到有使用 typedef 的, 也有使用 typedef typename 的,可参照如下代码。

1
2
3
4
5
6
typedef typename Registration<PointSource, PointTarget>::PointCloudSource PointCloudSource;
typedef typename PointCloudSource::Ptr PointCloudSourcePtr;
typedef typename PointCloudSource::ConstPtr PointCloudSourceConstPtr;

typedef PointIndices::Ptr PointIndicesPtr;
typedef PointIndices::ConstPtr PointIndicesConstPtr;

经过查阅一些资料,发现 typedef typename 本身并不是一个整体,typename 是用来修饰后面的名字的。使用 typename 可以指定后面的这个名字是类中的类型成员,而不是数据成员(如静态成员变量)。

如下例子所示,param 和 value 都是模板类 C 的模板形参,都有一个叫做 a_type 的成员,只不过一个用来做类型,一个用来做静态成员变量,都是合理的。所以在使用 typedef 定义 type2 时,必须要加上 typename,才能够说明这个模板形参的这个成员是类型,而不是静态成员变量。这个是必须加的,否则编译不通过。而且只需要对模板形参使用 typename,因为正常的类,编译器都是可以直接知道这是类型还是数据值的,如定义 type1 的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class A {
public:
typedef int a_type;
int a;
};

class B
{
B(){}

public:
static int a_type;

};

int B::a_type = 1;

template <typename param,typename value>
class C {
public:
typedef A::a_type type1; //正确,正常使用A的a_type类
typedef typename param::a_type type2;
//必须要在param 前面加上typename,向编译器说明这个模块类的a_type指的是一种类型

C(){
c = value::a_type;//模板形参的静态成员变量
}

int c;
};


int main() {

C<A,B> c;

return 0;
}

最后只需要记住一点,只有 typedef 后面的类型和模板形参相关,才需要使用 typename。否则编译器会自己判断的,不需要使用到 typename


C++ typedef typename 作用

C++ 的一些语法让人看着费解,其中就有:

1
typedef typename std::vector<T>::size_type size_type;

详见《C++ Primer》(第五版)P584

vector::size_type

明白上述语法,首先要先看清vector::size_type的意思。参考《STL 源码剖析》不难发现,其实:

1
2
3
4
5
6
7
template <class T,class Alloc=alloc>
class vector{
public:
//...
typedef size_t size_type;
//...
};

这样就看得很清晰了,vector::size_typevector的嵌套类型定义,其实际等价于 size_t类型。
也就是说:

1
2
3
vector<int>::size_type ssize;
//就等价于
size_t ssize;

为什么使用 typename 关键字

那么问题来了,为什么要加上 typename 关键字?

1
typedef std::vector<T>::size_type size_type;//why not?

实际上,模板类型在实例化之前,编译器并不知道vector<T>::size_type是什么东西,事实上一共有三种可能:

静态数据成员
静态成员函数
嵌套类型

那么此时typename的作用就在此时体现出来了——定义就不再模棱两可。

总结

所以根据上述两条分析,

1
typedef typename std::vector<T>::size_type size_type;

语句的真是面目是:
typedef创建了存在类型的别名,而typename告诉编译器std::vector<T>::size_type是一个类型而不是一个成员。

typedef 关键字

还是这段代码,我们添加一行:

1
2
3
4
5
6
7
8
9
#include <ext/alloc_traits.h>
using namespace std;

template<typename _Tp, typename _Alloc>
class AA
{
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;
_Tp_alloc_type tp;
};

这个 typedef 实际上就是给类型取了一个别名,将__gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other这么一长串的类型取了一个简单的别名,要是不取别名,这样长的类型真的是不忍直视。

当然啦,typedef 除了这种形式以外,其实很多时候也会给函数指针取别名哦,如下:

1
typedef int (*func)(int a, int b);

这个时候实际上就是给int * (int a, int b)这个函数指针取了一个别名 func。

using 关键字

对于 using 关键字,最开始知道是因为这行代码:

1
using namespace std;

所以它的第一个作用就是声明命名空间,使用形如using namespace 命名空间名;这样的形式告诉编译器,后续使用该命名空间里面的变量或者类型都无需再加上 std 的前缀,这个是对于命名空间整体的声明。

还有一种形式是:

1
2
using std::cout;
using std::endl;

这种就是只单独声明命名空间里面的某个名字,命名空间里面其他的东西是无法直接使用的,此时我们只能使用 cout 和 endl 这两个。

using 的第三种使用形式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class parent{
protected:
int m;
};

class child: public parent{
public:
using parent::m;
};

int main(){
child c;
c.m = 2;
return 0;
}

m 在 parent 里面是 protected 类型,但是在 child 里面使用 using 声明以后,它可以被直接访问,其实这个时候它的作用类似于引入命名空间中的变量,此处是引入父类中的保护类型成员变量,对于这种用法,我们不展开多说,只要知道有这样的作用,以后看到了这样的代码知道它是怎么个意思就行了。

using 在 c++11 以后又有了一种新的作用,那就是与 typedef 一样,给类型指定别名,形式是这样的:

1
using 别名=类型;

我们把上面 typedef 那里的代码改一下,如下:

1
2
3
4
5
6
7
8
9
#include <ext/alloc_traits.h>
using namespace std;

template<typename _Tp, typename _Alloc>
class AA
{
using _Tp_alloc_type = typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other;
_Tp_alloc_type tp;
};

对于函数指针,还可以这样:

1
using func = int (*)(int a, int b);

这样看来,using 感觉上比 typedef 更加直观一下哈。