移动语义
参考下面的案例
#include<iostream>
#include <cstring>
struct A{
char*ptr;
A():ptr(new char[4096]){}
A(const A& other){
std::cout<<"copy start"<<std::endl;
memcpy(ptr,other.ptr,4096);
}
~A(){
if(ptr) delete[]ptr;
}
};
A get_A(const A& a){
return a;
}
A make_A(){
A a;
return get_A(a);
}
int main(){
A a=make_A();
}
编译时加上-fno-elide-constructors避免RVO优化
g++ -fno-elide-constructors Mobile_semantics.cpp -o Mobile_semantics
./Mobile_semantics
可以看到发生了三次拷贝构造
- 第一次是get_A返回的A临时对象调用拷贝构造复制了a
- 第二次是make_A返回的A临时对象调用拷贝构造复制了get_A返回的临时对象
- 第三次是主函数a调用拷贝构造复制了make_A返回的临时对象
这段代码性能上存在极大的问题,产生的临时对象太多了,观察发现问题主要集中在第二次和第三次拷贝的过程,对此C++11引入了移动语义解决这个问题。
只需要对代码进行简单的修改即可减少大量资源消耗。
#include<iostream>
#include <cstring>
struct A {
char* ptr;
A():ptr(new char[4096]) {}
A(const A& other):ptr(new char[4096]) {
std::cout << "copy start" << std::endl;
memcpy(ptr, other.ptr, 4096);
}
A(A&&other){
std::cout << "move start" << std::endl;
ptr=other.ptr;
other.ptr=nullptr;
}
~A() {
if (ptr) delete[]ptr;
}
};
A get_A(const A& a) {
return a;
}
A make_A() {
A a;
return get_A(a);
}
int main() {
A a = make_A();
}
从深拷贝到指针变量的值交换毫无疑问移动语义的效率更高。
实际上移动语义的使用不仅仅局限在移动构造还要以下几种
- 移动赋值
class A{
A& operator=(A&&other){
ptr=other.ptr;
other.ptr=nullptr;
}
char *ptr;
};
- 利用move将左值转化为将亡值然后进行移动语义(这个将亡值生命周期和原来的左值一致,它不是实际的将亡值可以理解为)
struct B{
char *ptr;
B():ptr(new char[10]){}
B(B&&other){
std::cout << "move start" << std::endl;
ptr=other.ptr;
other.ptr=nullptr;
}
};
int main() {
B b;
B c=std::move(b);
if(!b.ptr) std::cout<<"move success"<<std::endl;
}
这种方法在stl迭代器部分应用较多,使得移动语义更加灵活。
完美转发
完美转发一句话就可以概括:使用万能引用的转发模板
在介绍完美转发前看一个案例
#include<iostream>
#include <cstring>
struct A {
char* ptr;
A():ptr(new char[4096]) {}
A(const A& other):ptr(new char[4096]) {
std::cout << "copy" << std::endl;
memcpy(ptr, other.ptr, 4096);
}
A(A&&other){
std::cout << "move" << std::endl;
ptr=other.ptr;
other.ptr=nullptr;
}
~A() {
if (ptr) delete[]ptr;
}
};
A make_A() {
return A();
}
template<class T>
void show_type(T t){}
template<class T>
void g(T t){
show_type(t);
}
int main(){
g(make_A());
A a;
g(a);
}
- 第一个move是make_A产生的
- 第二个move是g函数参数值拷贝产生的
- 第一个copy是show_type参数值拷贝产生的
- 第二个copy是g函数值拷贝产生的
- 第三个copy是show_type参数值拷贝产生的
可以看到转发的过程中如果使用值拷贝会产生大量的临时对象,这显然是需要想办法优化的,在C++11之前使用过常引用去优化,性能上没有问题但是常引用是不能修改的也造成了一些问题,好在万能引用的产生提供了一种新的解决方案。
使用万能引用作为转发模板的参数
#include<iostream>
#include <cstring>
struct A {
char* ptr;
A():ptr(new char[4096]) {}
A(const A& other):ptr(new char[4096]) {
std::cout << "copy" << std::endl;
memcpy(ptr, other.ptr, 4096);
}
A(A&&other){
std::cout << "move" << std::endl;
ptr=other.ptr;
other.ptr=nullptr;
}
~A() {
if (ptr) delete[]ptr;
}
};
A make_A() {
return A();
}
template<class T>
void show_type(T t){}
template<class T>
void g(T&&t){
show_type(static_cast<T&&>(t));
}
int main(){
g(make_A());
A a;
g(a);
}
第一个move是make_A产生的后面两个是转发后创建show_type的形参时产生的,可以看出完美转发保留了变量的左右值属性,从而极大的提高了转发效率。
同时需要注意的是完美转发可以保留变量的const和volatile属性,这一点从类型推导角度很好理解。
下面介绍完美转发封装的两个函数,直接看源码很好理解的
move和forward中都出现了remove_reference_t首先介绍一下它的作用
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
/*****/
template <class _Ty>
struct remove_reference {
using type = _Ty;
using _Const_thru_ref_type = const _Ty;
};
简单来说它的作用就是提供了具有高度一致性和可读性的类型接口,,需要注意的是接下来的remove_reference_t<_Ty>是一个整体。
remove_reference_t<_Ty>&是左值引用
remove_reference_t<_Ty>&&是右值引用
- move
// FUNCTION TEMPLATE move
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
通过参数类型可以看到move的作用是将接收到的左值或右值转化为右值
- forward
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
第一个forward的作用是将接收到的左值进行处理,折叠出来还是左值
接下来看第二个forward
首先is_lvalue_reference_v是模板变量,首先将is_lvalue_reference_v初始化为false然后下面模板进行偏特化如果是左值将is_lvalue_reference_v设置为true
template <class>
_INLINE_VAR constexpr bool is_lvalue_reference_v = false; // determine whether type argument is an lvalue reference
template <class _Ty>
_INLINE_VAR constexpr bool is_lvalue_reference_v<_Ty&> = true;
第二个forward的作用是对右值进行处理,与左值版本的forward相比它增加了一个一个处理,这个异常处理不是针对变量类型的而是针对模板类型的,<>里面不能是<T&>或是<const T&>,可以是<T&&>,这一点需要注意。
forward的作用就是作为完美转发的一个中间件,它的作用是将左右值等一系列属性进行还原,任何引用作为形参时函数内都会将它看作一个左值,为了避免覆盖右值属性所以需要进行完美转发,它的作用和static_cast<T&&>一致,forward的优点在于提供了更加规范的完美转发接口。