C++TinySTL

本文详细介绍了C++中STL的typename关键字用于解决模板中的依赖名称解析问题,traits编程的概念及其在自定义迭代器中的应用,以及std::remove_reference、std::move和std::forward在移动语义中的作用和实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个专栏用于整理学习STL过程中碰到的问题

  • typename
  • traits编程
  • std::remove_reference
  • std::move
  • std::forward

typename

STL中到处都是 typename …typedef typenmae … 的用法,但是一开始真的不知道是在搞些什么玩意。


 //略去了参数与函数体,返回值为什么要搞个typename?
typename iterator_traits<RandomIter>::difference_type
distance_dispatch(...){....}

//我们可以知道这是在定义类型别名,但是到底为什么要搞个typename??
 typedef typename value_traits_type::key_type    key_type;   

直到看了篇大佬的博客,现在正好整理一下…

我们在类外访问类内中的名称时,可以使用类作用域操作符,ClassName::name。且调用通常存在三种:静态数据成员,静态成员函数,嵌套类型。

struct MyClass{
	static int a;
	static int b();
	typedef itn c;
};

我们可能会定义这样的一种模板

template<class T>
void fun(){
	T::iterator *iter;
}

我们会很自然的认为这是在定义一个iter的指针,指向类作用域的iterator,像这样。

struct Ttype{
	struct iterator{/*...*/};
};

但是,如上述,我们的ClassName::name调用可能会有这样的情况。

struct Ttype{
	static int iterator;
};

那么类模板中的定义的 T::iterator *iter就成了一个乘法表达式…

由于这两种情况我们没有办法区分,所以,C++标准引入了typename

template<class T>
void fun(){
	typename T::iterator *iter;  //指定后面的对象(T::iterator)是一个类型
}

这样就不需要等到实例化的时候才可以区分两种情况

参见

  1. http://feihu.me/blog/2014/the-origin-and-usage-of-typename/

traits编程

当我们自定义迭代器时,可能会用到所指对象的类型(value type)。我们可以使用如下方法获取类型,并将其作为返回值。

template<class T>
struct MyIter{
	typedef T value_type;  //内嵌型别声明
	//....
};

typename MyIter::value_type 
func(MyIter poi){
	return *poi;   //返回类型为迭代器所指类型
}

注意返回类型中必须要加typename,指名后面的是一个类型。

如果我们定义了多种迭代器,且其中均有value_type的内嵌型别声明,则函数返回类型可泛化

template<class I>
typename I::value
func(I poi){
	return *poi;
}

同时,我们可以专门定义一个class template用于保存多种迭代器的特性。

template <class I>
struct iterator_traits{ //traits 即特性
	typedef typename  I::value_type value_type;
	//当然还有其它共有的特性,我们仅用value_type举例
}

现在,我们的func函数可继续更改。该函数接受不同的迭代器类型I,返回类型为迭代器I特化的iterator_traits类中的value_type,即迭代器I所指的对象。

template <class I>
typename iterator_traits<I>::value_type
func(I poi){
	return *poi;
}

加了一层iterator_traits有什么好处呢?我们接着看

template <class T>
struct iterator_traits<T*>{
	typedef T value_type;    
}

这个template是上面的iterator_traits类模板的偏特化版本,用于处理迭代器是原生指针的情况。因为我们没有办法为原生指针添加一个value_type内嵌类型,所以我们只能用另一个类将其提取出来。

int *p;
int i=func(p);
//int*的参数被传入偏特化的iterator_traits版本,T与value_type为int。

参见

  1. https://www.cnblogs.com/kanego/archive/2012/08/15/2639761.html
  2. STL源码剖析 第三章

std::remove_reference

std::move

std::forward

这仨玩意都定义于头文件**<type_traits>**

先看看std::remove_reference,这玩意定义的还挺简单。

//C++11起 声明如下
template< class T >
struct remove_reference;
//实现
//后两个为偏特化版本,分别接受引用类型与右值引用类型,但是无论接受什么类型
//T都是非引用的。
template< class T > struct remove_reference      {typedef T type;};
template< class T > struct remove_reference<T&>  {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};

测试
这里要注意std::is_same同样是一个模板,后面加()表示创建一个临时对象,这种用法在traits中非常常见。

#include<iostream>
#include<string>
#include<type_traits>
//#include "Iterator.h"
using namespace std;

template<class T1, class T2>
void print_is_same() {
	cout << is_same<T1, T2>() << endl;
}

int main() {
	print_is_same<int, int>();    //1
	print_is_same<int, int &>();  //0
	print_is_same<int, int &&>(); //0

	print_is_same<int, std::remove_reference<int>::type>();     //1
	print_is_same<int, std::remove_reference<int &>::type>();   //1
	print_is_same<int, std::remove_reference<int &&>::type>();  //1
	system("pause");
}

通过结果我们可以看到remove_reference移除了引用。

接下来是std::move,这个函数可以接受一个左值并将用右值引用对其绑定(通常情况下右值引用不可以绑定到变量,也可以说左值上)。我们来看看它的实现。

	template<class T>
	
	typename std::remove_reference<T>::type&& move(T&&arg)noexcept {	
		return static_cast<typename std::remove_reference<T>::type&&>(arg);
	}

函数返回一个(移除引用的类型的)右值引用,参数后加noexcept表示该函数不抛出异常(为什么这么搞我反正在书上见过)。参数arg是一个右值引用,我们向其传递左值实参的时候,会发生什么呢?

引用折叠
我们之前有说过,不允许将右值引用绑定到左值上,但是有两个例外。

  1. 当我们将左值传递给函数的右值引用参数,且!,右值引用指向模板类型参数时,编译器推断模板参数为实参的左值引用类型。我们假定实参为int M,则模板参数为M&,即T的类型为int&。
  2. 如果我们创建了一个引用的引用,则这些引用会“折叠”,折叠规则为:
    X& &和 X& && 和X&& &折叠为 X&。
    X&& &&折叠为 X&&。

好了我们现在知道T是 int&(1中我们假定了函数实参是 int M),所以形参T&&arg就成了int& &&arg,折叠成int& arg,int型的左值引用,问题解决。

要注意折叠引用只能应用于间接创建的引用的引用,如类型别名或模板参数。

参见

  1. C++Primer 第五版 p471 p608
  2. https://zh.cppreference.com/w/cpp/types/remove_reference
  3. https://zh.cppreference.com/w/cpp/utility/move

最后是std::forward

我们来看看这段代码

template<class I>
void fun(I&&) {
	cout << "You called I&&" << endl;
}

template<class I>
void fun(I&) {
	cout << "You called I&&" << endl;
}

template<class T>
void wrapper(T&& t) {
	fun(t);
	//fun(std::forward<T>(t));
}

int main() {
	int c = 1;
	wrapper(c+1);
	system("pause");
}

main函数调用wrapper函数,向其传递一个int&&的实参。则wrapper函数中T被推导为int,是个左值,所以这个fun调用可以同时匹配上面的两个重载版本,编译器报错:“对重载函数的调用不明确”。
这其中有一个问题,我们调用wrapper的时候,传递的是int&&实参,但是这个参数,在被wrapper转发给fun时,变成了int。我们有什么办法可以阻止这种情况,即,我们怎样能保证,wrapper中给fun函数传递的参数类型与main中传递给wrapper函数的参数类型一致呢?

C++11为我们提供了forward,同样是上面的代码,我们注释掉wrapper中的func(t),取消fun(std::forward(t));的注释。再次运行,fun匹配的版本只有参数为I&&的版本。因为现在wrapper调用fun传递到参数类型仍是int&&。

知道了用法,我们再来看看它的实现.。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值