仿写实现move函数
一、值的类型
1.左值
描述:能够取地址的值成为左值
int a = 10;
const int b = 15;
int *pa = &a;
const int *pb = &b;
2.纯右值
描述:赤裸裸的字面值 eg(false , 3 , 12.23等)
int a = 13;
int *p = &a; //取a的地址
int *ptr = &13; //错误,13是一个字面量,无法取地址
如字面常量 103, 12.23, nullptr;不可以取地址, 称为为纯右值。
3.将亡值
描述:表达式的运行或计算过程中所产生的临时量或临时对象,称之为将亡值;临时量有可能是字面值,也有可能是一个不具名的对象。
算术表达式(a+b …),逻辑表达式(a || b …),比较表达式(a != b …)取地址表达式(&b)等,产生的计算结果相当于字面量,实际存储在 cpu的寄存器中 ,因为计算结果是字面量,所以为纯右值。
如图
因为c++是面向对象的,所以 i++ 和 ++i 是不一样的, ++i 相当于 i = i + 1; 所以 不会产生临时变量, 而 i++不同,它相当于 将 i 拷贝一份当做副本,然后将原来的 i 进行加 1 操作,最后将 副本 的值返回。 副本是不具名的,所以是 将亡值,而 返回的值是 字面量 所以 i++ 为纯右值 。
不具名对象如图
编译后会报错 test.cpp(17): error C2102: “&”要求左值,。
因为 Int(13) 程序运行过程中所产生了不具名对象,将亡值。 不可以取地址, 所以&Int(13)错误 。
int fun()
{
int value = 10;
return value;
}
int main()
{
int i = 0;
int a = 1;
i = a + b;
i++;
&b;
a = fun(); //返回时在主栈帧中构造临时量,是将亡值,纯右值。
return 0;
}
流程图 图 2.2
当fun函数return 时 ,其栈帧空间会被回收,此时先在主栈帧中创建一个将亡值对象(xvalue) 将返回的值赋给将亡值,回到主函数栈帧中会再将将亡值对象的值赋给 a.
二、引用
基本原则:
1.声明引用的时候必须初始化,且一旦绑定,不可把引用绑定到其他对象;即引用必须初始化,不能对引用重定义
2.对引用的一切操作,就相当于对原对象的操作
1.引用与函数重载
void fun(int& val)
{
cout << "LeftRef" << endl;
}
void fun(const int & val)
{
cout << "Const LeftRef" << endl;
}
void fun(int&& val)
{
cout << "RightRef" << endl;
}
int main()
{
int a = 10;
const int d = 15;
int&& f = 12; //右值引用f是具名右值引用,编译器会将已命名的右值引用视为左值
//int&& g = f; // error,f为具名右值
fun(a);
fun(d);
fun(f);
fun(13);
return 0;
}
运行结果:
结论:
1.int& a = 10; 右值引用 a 是具名右值引用。
2.编译器会将已命名的右值引用视为左值,所以不能 int && b = a;
2.函数返回值
int func()
{
int x = 10;
cout<<"&x: "<<endl;
return x;
}
int main()
{
int a = func();
//int& b = func(); // error; func返回值为右值.
const int& c = func();
cout << "&c: " << &c << endl;
int&& d = func();
cout << "&d: " << &d << endl;
return 0;
}
运行结果:
结论:1.函数返回值为将亡值(右值)
2.常性左值引用为万能引用,可以接受右值
3.自定义类
class Int
{
int val;
public:
Int(int x = 0) :val(x)
{
cout << "Create Int: " << this << endl;
}
Int(const Int& it) :val(it.val)
{
cout << this<<" Copy Create Int: <= " << &it << endl;
}
Int(Int&& it) :val(it.val)
{
it.val = 0;
cout << this<<" Move Create Int: <= " << &it << endl;
}
Int& operator=(const Int& it)
{
if (this == &it) return *this;
val = it.val;
cout << this << " = " << &it << endl;
return *this;
}
Int& operator=(Int&& it)
{
if (this == &it) return *this;
val = it.val;
it.val = 0;
cout << this << " <= " << &it << endl;
return *this;
}
~Int() { cout << "Destroy Int: " << this << endl; }
};
Int func(int x)
{
Int tmp(x);
return tmp;
}
int main()
{
Int a = func(1);
cout << "--------------------" << endl;
Int x(0);
cout << "--------------------" << endl;
x = func(2);
cout << "--------------------" << endl;
//Int& b = func(3);
const Int& c = func(4);
cout << "--------------------" << endl;
Int&& d = func(5);
cout << "--------------------" << endl;
Int f(a);
cout << "--------------------" << endl;
return 0;
}
运行结果
结论:
1.通过右值引用,比之前少了一次移动构造和一次析构,原因在于右值引用绑定了右值,让临时右值的生
命周期延长了 <主栈帧里创建的不具名对象(将亡值)>
2.函数返回值构建过程和之前分析的一样( 图 2.2 )
三、std::move的实现
原理:
本质上是将左值强制转换为右值引用,调用对象的移动构造和移动赋值函数,实现对资源的转移。
优点:
当一个对象内部有较大的堆内存或者动态数组时,进行深拷贝会占用cpu资源,而浅拷贝释放资源时会造成对堆区进行重复释放导致非法访问。使用move()语义可以提高性能
使用范围:
move 对于拥有形如对内存、堆区等资源的成员的对象有效
1.未定义的引用类别 && (函数模板中)
template <class _Ty>
void fun(_Ty&& x) //未定义的引用类型,它必须被初始化,它是左值还是右值引用,取决于它的初始化
{
int z = 10;
_Ty y = z;
}
int main()
{
int a = 1;
const int b = 2;
int& c = a;
const int& d = b;
fun(10);
fun(a);
fun(b);
fun(c);
fun(d);
return 0;
}
fun(a),fun©: x 的类型为 int& ,y的类型为 int&;
fun(b),fun(d):x的类型为 const int& ,y的类型为const int&;
fun(10):x的类型为 int&& , y 的类型为 int,不具有引用;
结论:
-
_Ty && 与左值,普通左值引用结和,_Ty为左值引用
-
_Ty&& 与左值常引用结合,_Ty为左值常性引用
-
_Ty&& 与右值结合时,_Ty只保留类型,不具有引用属性
-
_Ty&& 不会破坏掉 对象的const属性
实现流程:
1.我们需用去除对象的引用属性
template <class _Ty>
struct my_remove_reference
{
using type = _Ty;
};
template <class _Ty>
struct my_remove_reference<_Ty &>
{
using type = _Ty;
};
template <class _Ty>
struct my_remove_reference<_Ty &&>
{
using type = _Ty;
};
2.加入适配器,简化代码
template <class _Ty>
using my_remove_reference_t = typename my_remove_reference<_Ty>::type;
3.转换为右值
template <class _Ty>
my_remove_reference_t<_Ty> && my_move(_Ty &&x)
{
return static_cast<my_remove_reference_t<_Ty>&&>(x);
}
典型错误:使用c语言中的强制类型转换
template <class _Ty>
my_remove_reference_t<_Ty> && my_move(_Ty &&x)
{
return (my_remove_reference_t<_Ty>&&)x; //error,不能使用c语言中的强制类型转换
}
因为常性对象的资源是不能进行移动修改的,而强制类型转换会破坏这一平衡点,而c++的静态类型转换刚好不会去掉const属性。
四、实现forward函数
原理:
利用引用叠加,按照参数原来的值类型转发到另一个函数
先来看std::forward()函数示例
void print(int& val)
{
cout<<"LReference"<<endl;
}
void print(const int& val)
{
cout<<"const LReference"<<endl;
}
void print(int&& val)
{
cout<<"RReference"<<endl;
}
template<class _Ty>
void fun(_Ty&& val)
{
print(std::forward<_Ty>(val));
}
int main()
{
int a = 10;
const int b = 20;
int& c = a;
fun(a);
fun(b);
fun(c);
fun(10);
system("pause");
}
运行结果
引用叠加:
由于存在 (_Ty&&) 这种未定的引用类型,当它作为参数时,可能被一个左值引用或者右值引用的参数初始化,这时经过类型推导的 _Ty 和右值引用(&&)叠加会发生类型的变化,这种变化被称为引用折叠。
即 static_cast<_Ty &&> (val);
c++中规定:
-
所有的右值引用叠加到右值引用上仍然还是一个右值引用。
-
**所有的其他引用类型之间的叠加都将变成左值引用。
设计forward的参数
-
需要接受所有类型的数据 -> 联想到万能引用
-
需要保留原来数据的类型 -> 联想到去除引用属性的模板
去除引用属性的模板代码在上文move()函数实现中
1.左值版
template <class _Ty>
_Ty&& my_forward(my_remove_reference_t<_Ty>& Arg)//_Ty保留数据原来的属性
{
return static_cast<_Ty &&> (Arg); //此处涉及引用折叠
}
2.右值版 (forward还应支持右值参数)
template <class _Ty>
_Ty&& my_forward(my_remove_reference_t<_Ty>&& Arg)
{
return static_cast<_Ty &&> (Arg); //此处涉及引用折叠
}
这样就可以实现和系统的forward相同的功能
测试代码
template <class _Ty>
struct my_remove_reference
{
using type = _Ty;
};
template <class _Ty>
struct my_remove_reference<_Ty &>
{
using type = _Ty;
};
template <class _Ty>
struct my_remove_reference<_Ty &&>
{
using type = _Ty;
};
template <class _Ty>
using my_remove_reference_t = typename my_remove_reference<_Ty>::type;
template <class _Ty>
_Ty &&my_forward(my_remove_reference_t<_Ty> &_Arg)
{
return static_cast<_Ty &&>(_Arg);
}
template <class _Ty>
_Ty &&my_forward(my_remove_reference_t<_Ty> &&_Arg)
{
return static_cast<_Ty &&>(_Arg);
}
void print(int& val)
{
cout<<"LReference"<<endl;
}
void print(const int& val)
{
cout<<"const LReference"<<endl;
}
void print(int&& val)
{
cout<<"RReference"<<endl;
}
template<class _Ty>
void fun(_Ty&& val)
{
print(my_forward<_Ty>(val));
}
int main()
{
int a = 10;
const int b = 20;
int& c = a;
fun(a);
fun(b);
fun(c);
fun(10);
print(my_forward(10));//右值版
}