1.仿写std::move
// 1
template<typename _Ty>
struct my_remove_reference
{
using type = _Ty;
using _Const_thru_ref_type = const _Ty;
};
// 2
template<typename _Ty>
struct my_remove_reference<_Ty&>
{
using type = _Ty;
using _Const_thru_ref_type = const _Ty&;
};
// 3
template<typename _Ty>
struct my_remove_reference<_Ty&&>
{
using type = _Ty;
using _Const_thru_ref_type = const _Ty&&;
};
// 4
template<typename _Ty>
using my_remove_reference_t = typename my_remove_reference<_Ty>::type;
// 5
template<typename _Ty>
my_remove_reference_t<_Ty>&& my_move(_Ty&& _Arg)
{
return static_cast<my_remove_reference_t<_Ty>&& >(_Arg);
}
原理:有三个版本的用作萃取的类模板
- 泛化版本,用type表示原始类型;
- 接收引用的部分特化版本,用type表示原始类型;
- 接收右值引用的部分特化版本,依旧用type表示原始类型。
- 再通过第四个模板类 获取原始类型type,将其重命名为my_remove_reference。
- 此时第五个模板类的_Ty就表示最原始的类型,然后利用static_cast将其强转为右值引用,但这里面无法去除常性(const)。
2.完美转发
move语义是不管传入什么值,都萃取处原始类型,完美转发是变量原本是什么类型,在经过一系列函数调用后还保留原始属性(左值右值,纯右值)。
template<typename _Ty>
_Ty&& my_forward(my_remove_reference_t<_Ty>& _Arg) noexcept
{
return static_cast<_Ty&&>(_Arg);
}
template<typename _Ty>
_Ty&& my_forward(my_remove_reference_t<_Ty>&& _Arg) noexcept
{
return static_cast<_Ty&&>(_Arg);
}
示例:
void print(int& a)
{
cout << "int & " << endl;
}
void print(const int& a)
{
cout << "const int & " << endl;
}
void print(int&& a)
{
cout << "int &&" << endl;
}
template<typename _Ty>
void fun(_Ty&& a)
{
print(my_forward<_Ty>(a));
}
int Add(int a, int b)
{
return a + b;
}
int main(void)
{
int a = 10;
int& b = a;
const int& c = a;
int&& d = 10;
fun(a);
fun(b);
fun(c);
fun(d);
fun(20);
fun(Add(a, b));
return 0;
}
在这个例子中,主函数调用的6个fun() 函数,传入的参数经过完美转发仍然保持其原本属性。
-
为什么d引用了纯右值,但却打印的 “int&”,因为引用了纯右值,这个纯右值就具有了名字,可以对其寻址,那它已经是左值了。
-
为什么最后一个fun()函数调用的Add()函数,传入的左值,但打印结果是右值? 因为在Add()函数return时是返回了一个将亡值,对其按照右值的特性来看,所以调用的右值的print().
3. auto关键字
- auto在C++中的意义和C语言中的不同,它是表示自动类型推演。
可以这样用:
auto x = 10; // x->int
auto f = 12.23; // f->double
auto ip = new int(10); // ip->int*
auto* op = new int(10); // op->int
- auto在使用时不可以“分身”,示例:前面一个变量推演出auto是int,但变量u是浮点型,auto不能推演,所以说不能分身。
原因就是auto在编译过程中就会替换成推演到的类型,执行到u这一步时类型不匹配了。
- 对常变量的推演
如果是拿常性变量给auto类型的变量赋值,那么auto默认是整型的;
但如果是auto类型的引用,那么auto也会推演成常性变量(const int)
const int x = 10;
auto a = x; // auto->int
auto& b = x; // auto ->const int
- auto的限制:1.不允许作为函数参数;2.不能以auto声明类里的数据成员(除非是静态常性的整型);3.不能声明集合(例如数组这样的)。
示例:
4. decltype
这个关键字也是类型推演,推演的是表达式的类型。可以利用这个特点声明变量。示例:
int main(void)
{
int a = 10;
decltype(a) b = 20;
decltype(a + b) c = 10;
}
5. NULL和nullptr
先来看看NULL的定义是怎样的:
如果在Cpp中,定义NULL为0,否则为无类型指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
那么在下面这个例子中就会产生二义性:
void print(char* a)
{
cout << "char* a" << endl;
}
void print(int a)
{
cout << "int a" << endl;
}
int main(void)
{
print(NULL);
print(0);
return 0;
}
可以发现传进去的参数都是整型,就是把NULL当成0来处理了。
再来看nullptr:
void print(char* a)
{
cout << "char* a" << endl;
}
void print(int a)
{
cout << "int a" << endl;
}
int main(void)
{
print(nullptr);
print(NULL);
print(0);
return 0;
}
原因是nullptr是C++中的一个关键字,意思是一个空指针常量。来看看它是怎么定义的:转到头文件<stddef.h>
#ifdef __cplusplus
namespace std
{
typedef decltype(__nullptr) nullptr_t;
}
using ::std::nullptr_t;
#endif
在该头文件中可以看到nullptr的定义,最骚的是,这个东西竟然是先有值,再有这种类型,意思是可以这样定义变量了?示例:
int main(void)
{
typedef decltype(nullptr) nullptr_t;
nullptr_t a;
return 0;
}
在调试过程中发现,定义的变量就是无类型指针(void*)。。。