C++ typedef typename 用法
在 c++ 的标准库中,因为类继承关系比较复杂和模板使用比较多的原因,源代码中充斥着 typename、typedef 和 using 这三个关键字
typename 关键字
typename 的第一个作用是用作模板里面,来声明某种类型,比如这样的:
1 | template<typename _Tp, typename _Alloc> |
最开始的时候声明模板形参,也会使用 class,但我们都知道 class 总要是用来指定一个类名,据说是为了避免混淆,所以后来增加了 typename 这个关键字,它告诉编译器,跟在它后面的字符串是一个不确定的类型,而不是变量或者其他什么东西。
typename 在 stl 中还有另外一种作用,显示地告诉编译器一个名字表示的是类型。
1 | //test.cpp |
这里顺便说一下 rebind 前面为啥要放一个 template,它是为了告诉编译器后面的 <> 是用于指定模板参数,而进行比较。
这个时候我们使用g++ -c test.cpp -o test.o
是可以编译通过的,但如果我们去掉第三个 typename 看,会发生什么呢?
再次编译,报错如下:
1 | test.cpp:8:10: 错误:‘typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Tp>::other’之前需要‘typename’,因为‘typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Tp>’是一个有依赖的作用域 |
编译器直接指明了需要一个 typename,实际上 typename 在这里也是指定它后面的字符串为类型,这是因为对于形如AA::BB
这样的形式,它有可能是一个类型,也有可能是类的静态成员,这个时候加上 typename 就是为了告诉编译器,它后面的一大串字符串都是一个类型。
在阅读一些代码是时常看到有使用 typedef 的, 也有使用 typedef typename 的,可参照如下代码。
1 | typedef typename Registration<PointSource, PointTarget>::PointCloudSource PointCloudSource; |
经过查阅一些资料,发现 typedef typename 本身并不是一个整体,typename 是用来修饰后面的名字的。使用 typename 可以指定后面的这个名字是类中的类型成员,而不是数据成员(如静态成员变量)。
如下例子所示,param 和 value 都是模板类 C 的模板形参,都有一个叫做 a_type 的成员,只不过一个用来做类型,一个用来做静态成员变量,都是合理的。所以在使用 typedef 定义 type2 时,必须要加上 typename,才能够说明这个模板形参的这个成员是类型,而不是静态成员变量。这个是必须加的,否则编译不通过。而且只需要对模板形参使用 typename,因为正常的类,编译器都是可以直接知道这是类型还是数据值的,如定义 type1 的方式。
1 | class A { |
最后只需要记住一点,只有 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 | template <class T,class Alloc=alloc> |
这样就看得很清晰了,vector::size_type
是vector
的嵌套类型定义,其实际等价于 size_t
类型。
也就是说:
1 | vector<int>::size_type 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 |
|
这个 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 | using std::cout; |
这种就是只单独声明命名空间里面的某个名字,命名空间里面其他的东西是无法直接使用的,此时我们只能使用 cout 和 endl 这两个。
using 的第三种使用形式是:
1 | class parent{ |
m 在 parent 里面是 protected 类型,但是在 child 里面使用 using 声明以后,它可以被直接访问,其实这个时候它的作用类似于引入命名空间中的变量,此处是引入父类中的保护类型成员变量,对于这种用法,我们不展开多说,只要知道有这样的作用,以后看到了这样的代码知道它是怎么个意思就行了。
using 在 c++11 以后又有了一种新的作用,那就是与 typedef 一样,给类型指定别名,形式是这样的:
1 | using 别名=类型; |
我们把上面 typedef 那里的代码改一下,如下:
1 |
|
对于函数指针,还可以这样:
1 | using func = int (*)(int a, int b); |
这样看来,using 感觉上比 typedef 更加直观一下哈。