C++:完美转发和移动语义

移动语义

参考下面的案例

#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();
}

在这里插入图片描述
从深拷贝到指针变量的值交换毫无疑问移动语义的效率更高。

实际上移动语义的使用不仅仅局限在移动构造还要以下几种

  1. 移动赋值
class A{
	A& operator=(A&&other){
		ptr=other.ptr;
		other.ptr=nullptr;
	}
	char *ptr;
};
  1. 利用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>&&是右值引用

  1. 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的作用是将接收到的左值或右值转化为右值

  1. 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的优点在于提供了更加规范的完美转发接口。

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值