std::forward完美转发(建议在std::move之后看)

本文详细介绍了C++中的完美转发std::forward,包括其背景、实现原理和实际使用案例。通过std::forward,可以确保参数在传递过程中保持其原始的左值或右值属性,从而提高性能,特别是在需要高效创建对象和使用移动语义的场景下。

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

参考https://blog.csdn.net/s11show_163/article/details/114296006

一句话概括std::forward ———— 所谓的完美转发,是指std::forward会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。

一句话概括std::move ———— std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。
std::move的实现:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type &&>(t);
}

std::forward实现如下:

template <class T>
T&& forward(typename tinySTL::remove_reference<T>::type& t) noexcept {
    return static_cast<T&&>(t);
}

template <class T>
T&& forward(typename tinySTL::remove_reference<T>::type&& t) noexcept {
    return static_cast<T&&>(t);
}

看看remove_reference 的做了什么
他封装了一个普通的模板类,并且typedef T type,主要看第二个,封装了一个引用类型的T&
我们使用时remove_reference<decltype(*a)>,就会被传到第二个实现中。
remove_reference<int &> ,那么typedef int type,此时type就会变为int,解除引用。
remove_reference<decltype(*a)>::type b 翻译过来就是int b
那同理typename tinySTL::remove_reference::type& t翻译过来就是int& t

int main()
{

  int a[] = {1,2,3};
  remove_reference<decltype(*a)>::type b = a[0];
  a[0] = 4;
  cout << b; //输出1
  return 0;
}

当t是左值引用时,此时推到得到T = X&,则T&&展开为X& &&,经过引用折叠后得到X&,即最后返回static_cast<X&>(t)。

当t是右值引用时,此时推到得到T = X&&,则T&&展开为X&& &&,经过引用折叠后得到X&&,即最后返回static_cast<X&&>(t)。
完美转发使用两步来完成任务:

1. 在模板中使用&&接收参数。
2. 使用std::forward()转发给被调函数.

这样左值作为仍旧作为左值传递,右值仍旧作为右值传递!

C++中存在两个比较难理解的操作:

  • std::move : 移除左值属性。
  • std::forward : 完美转发。
    下面来讲一讲std::forward的用途和转换原理。

1. 背景

假如我们封装了一个操作,主要是用来创建对象使用(类似设计模式中的工厂模式),这个操作如下:

可以接受不同类型的参数,然后构造一个对象的指针。
性能尽可能高。
现在假设这个类的定义如下:

class CData
{
public:
	CData() = delete;
	CData(const char* ch) : data(ch)
	{
		std::cout << "CData(const char* ch)" << std::endl;
	}
	CData(const std::string& str) : data(str) 
	{
		std::cout << "CData(const std::string& str)" << std::endl;
	}
	CData(std::string&& str) : data(str)
	{
		std::cout << "CData(std::string&& str)" << std::endl;
	}
	~CData()
	{
		std::cout << "~CData()" << std::endl;
	}
private:
	std::string data;
};

这里如果需要高效率,对于右值的调用应该使用CData(std::string&& str) : data(str)移动函数操作。

1.1 普通模板定义

如果只要一个函数入口来创建对象,那么使用模板是不错的选择,例如可以如下:

template<typename T>
CData* Creator(T t)
{
	return new CData(t);
}
void Forward()
{
	const char* value = "hello";
	std::string str1 = "hello";
	std::string str2 = " world";
	//CData* p = Creator(value);
	CData* p = Creator(str1);
	//CData* p = Creator(str1 + str2);

	delete p;
}

这种办法虽然行得通,但是比较挫,因为每次调用Creator(T t)的时候,都需要拷贝内存,明显不满足高效的情况。

1.2 引用模板定义

上面说的值拷贝不能满足内容,那么我们使用引用就可以解决问题了吧?

template<typename T>
CData* Creator(T& t)
{
	return new CData(t);
}
void Forward()
{
	const char* value = "hello";
	std::string str1 = "hello";
	std::string str2 = " world";
	CData* p = Creator(value);
	//CData* p = Creator(str1);

	delete p;
}

但是这种情况对于CData* p = Creator(str1 + str2)无法解决问题,因为右值无法赋值到左值的引用。

1.3 右值引用模板

从上面我们比较容易想到,使用&&来解决拷贝的问题。

template<typename T>
CData* Creator(T&& t)
{
	return new CData(t);
}
void Forward()
{
	const char* value = "hello";
	std::string str1 = "hello";
	std::string str2 = " world";
	CData* p = Creator(str1 + str2);

	delete p;
}

由于模板中引用折叠的规则:
T& & –> T&。
T&& & –> T&。
T& && –>T&。
T&& && –> T&&。
这种使用基本能够满足使用了。但是真的吗?别急我们来分析一下效率问题。

对于CData* p = Creator(str1 + str2);的调用:

产生一个右值str1 + str2.
CData* Creator(T&& t)右值引用,此时为:std::string&& t。
那么return new CData(t);为右值引用,调用构造函数的CData(std::string&& str) : data(str)移动构造函数。
但是是否是这样的呢?如果是这样效率确实是最佳的,因为我们只需要右值直接移动就是最高效率了,运行结果如下:

CData(const std::string& str)
~CData()

显然并没有调用移动函数,原因是因为在函数:

CData* Creator(T&& t)
{
	return new CData(t);
}

t 是一个变量,为左值,无论左值引用类型的变量还是右值引用类型的变量,都是左值,因为它们有名字。例如可以写如下代码:

int a = 100;
int&& b = 100;
int& c = b; //正确,b为左值
int& d = 100; //错误

2. forward完美转发

我们使用fordward来完美解决这个问题。

2.1 完美转发模板

template<typename T>
CData* Creator(T&& t)
{
	return new CData(std::forward<T>(t));
}

void Forward()
{
	const char* value = "hello";
	std::string str1 = "hello";
	std::string str2 = " world";
	CData* p = Creator(str1 + str2);

	delete p;
}

此时运行结果为:

CData(std::string&& str)
~CData()

2.2 结论

所谓的完美转发,是指std::forward会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。

这个对于上面一个例子带来的好处就是函数转发仍旧为右值引用,可以使用移动函数。

2.3 原理

std::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));
}

CData* Creator(T&& t)
{
	return new CData(std::forward<T>(t));
}

如果T为std::string&,那么std::forward(t) 返回值为std::string&&&, 折叠为std::string&,左值引用特性不变。
如果T为std::string&&,那么std::forward(t) 返回值为std::string&&&&, 折叠为std::string&&,右值引用特性不变。
如果调用者为std::string,调用将会转换成为std::string&,为类型1.

3. 完美转发的实际使用

完美转发在C++标准库中使用特别多,下面举一个例子:

3.1 make_shared

这个用来创建一个对象的智能指针,并且可以通过使用不同的参数来构造所创建对象的指针,这个函数实现如下:

template<class _Ty,
	class... _Types>
_NODISCARD inline shared_ptr<_Ty> make_shared(_Types&&... _Args)
{	// make a shared_ptr
	const auto _Rx = new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);  //参数完美转发

	shared_ptr<_Ty> _Ret;
	_Ret._Set_ptr_rep_and_enable_shared(_Rx->_Getptr(), _Rx);
	return (_Ret);
}

我们将模板参数全部完美转发给构造函数,类似的还有个tuple这些标准库。

4. 总结

完美转发使用两步来完成任务:

在模板中使用&&接收参数。
使用std::forward()转发给被调函数.
这样左值作为仍旧作为左值传递,右值仍旧作为右值传递!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值