C++之std::forward完美轉(zhuǎn)發(fā)的實(shí)現(xiàn)
1.簡(jiǎn)介
std::forward是C++11引入的函數(shù)模板,它的作用是實(shí)現(xiàn)函數(shù)參數(shù)的完美轉(zhuǎn)發(fā),通俗的講就是根據(jù)傳入的參數(shù),決定將參數(shù)以左值引用還是右值引用的方式進(jìn)行轉(zhuǎn)發(fā)。
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);
}
//右值版本
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);
}2.為什么需要完美轉(zhuǎn)發(fā)?
在C++中,函數(shù)參數(shù)的值的類(lèi)別(左值或者右值)和類(lèi)型信息在傳遞過(guò)程中可能會(huì)丟失,以下方函數(shù)為例:
#include<iostream>
void target(int& x) { std::cout << "lvalue\n"; }
void target(int&& x) { std::cout << "rvalue\n"; }
template<typename T>
void forwarder(T x) {
target(x); // 無(wú)論傳入的是左值還是右值,x都是左值, 因?yàn)閤是可以取地址的
}
int main() {
int a = 10;
forwarder(a); // 期望調(diào)用 target(int&)
forwarder(10); // 期望調(diào)用 target(int&&)
return 0;
}在上面代碼中,forwarder函數(shù)無(wú)法區(qū)分傳入的參數(shù)是左值還是右值,因?yàn)閤在函數(shù)內(nèi)部總是左值,想對(duì)這個(gè)進(jìn)行區(qū)分,就需要引入完美轉(zhuǎn)發(fā)。
3.完美轉(zhuǎn)發(fā)原理
在C++中,存在左值(lvalue)和右值(rvalue)的概念。關(guān)于左值和右值的概念,自己可以查詢相關(guān)資料。簡(jiǎn)單來(lái)說(shuō),左值是指可以取地址的、具有持久性的對(duì)象,而右值是指不能取地址的、臨時(shí)生成的對(duì)象。傳統(tǒng)上,當(dāng)一個(gè)左值傳遞給一個(gè)函數(shù)時(shí),參數(shù)會(huì)以左值引用的方式進(jìn)行傳遞;當(dāng)一個(gè)右值傳遞給一個(gè)函數(shù)時(shí),參數(shù)會(huì)以右值引用的方式進(jìn)行傳遞。
以VS2019的std::forward源碼實(shí)現(xiàn)來(lái)講解:

1. 當(dāng)傳遞給func函數(shù)的實(shí)參類(lèi)型為左值QObject時(shí),T被推導(dǎo)為QObject&類(lèi)別。然后forward會(huì)實(shí)例化為std::forward<QObject&>,并返回QObject&(左值引用,根據(jù)定義是個(gè)左值!)
2. 而當(dāng)傳遞給func函數(shù)的實(shí)參類(lèi)型為右值QObject時(shí),T被推導(dǎo)為QObject。然后forward被實(shí)例化為std::forward<QObject>,并返回QObject&&(注意,匿名的右值引用是個(gè)右值?。?/p>
3. 可見(jiàn),std::forward會(huì)根據(jù)傳遞給func函數(shù)實(shí)參(注意,不是形參)的左/右值類(lèi)型進(jìn)行轉(zhuǎn)發(fā)。當(dāng)傳給func函數(shù)左值實(shí)參時(shí),forward返回左值引用,并將該左值轉(zhuǎn)發(fā)給doWork。而當(dāng)傳入func的實(shí)參為右值時(shí),forward返回右值引用,并將該右值轉(zhuǎn)發(fā)給doWork函數(shù)。
4.完美轉(zhuǎn)發(fā)失敗的情形
在std::forward中,失敗的情況其實(shí)就是完美轉(zhuǎn)發(fā)的目的沒(méi)有達(dá)到,甚至根本無(wú)法實(shí)現(xiàn)
1.花括號(hào)的初始化方式調(diào)用
#include <iostream>
#include <array>
void test(const std::array<int,5> ¶m){}
template<typename T>
void func(T&& t)
{
test(std::forward<T>(t));
}
int main()
{
std::array<int, 5> arr = {0,1,2};
func(arr);
// func({0,1,2,3,4});
}解決方案:先用auto聲明一個(gè)局部變量,再將該局部變量傳遞給轉(zhuǎn)發(fā)函數(shù)。
2.或者NULL做為空指針傳遞
其實(shí)這個(gè)也是c++11后強(qiáng)調(diào)使用nullptr做為指針的空值的一個(gè)重要原因。之所以會(huì)這樣,是因?yàn)?和NULL往往會(huì)被默認(rèn)轉(zhuǎn)成為int類(lèi)型,這也是在早期的C編譯器中經(jīng)常遇到的一個(gè)編譯現(xiàn)象,就是“XXX無(wú)法轉(zhuǎn)成int”其實(shí)就是寫(xiě)錯(cuò)了,但默認(rèn)就是往int上靠,這個(gè)沒(méi)辦法。一如下面:
void test( void *ptr){}
template<typename T>
void func(T&& t)
{
test(std::forward<T>(t));
}
int main()
{
func(0);
}解決方案:傳遞nullptr,而非0或NULL
3.static const的應(yīng)用(含constexpr)
void test(int i){}
class Data {
public:
static const int d_ = 1;
};
//const int Data::d_ ;
template<typename T>
void func(T&& t)
{
test(std::forward<T>(t));
}
int main()
{
func(Data::d_);
}
這段代碼在VS中可以成功運(yùn)行,但在g++中會(huì)報(bào)一個(gè)鏈接錯(cuò)誤“ undefined reference to Data::d_ collect2: error: ld returned 1 exit status”。VS中對(duì)編譯和鏈接相對(duì)來(lái)說(shuō)還是寬松一些。
想通過(guò)連接只需要把注釋的部分解開(kāi)就可以了。
解決方案:在類(lèi)外定義該成員變量。注意這聲變量在聲明時(shí)一般會(huì)先給初始值。因此定義時(shí)無(wú)需也不能再重復(fù)指定初始值
4.對(duì)重載和模板的函數(shù)名處理
這句話的意思是指在完美轉(zhuǎn)發(fā)時(shí)如果參數(shù)是一個(gè)函數(shù)名稱,那么如果這個(gè)函數(shù)名稱的函數(shù)如果存在重載(或是模板)的話,在普通編程的直接調(diào)用情況下是沒(méi)有問(wèn)題,但是在完美轉(zhuǎn)發(fā)時(shí),不管是直接調(diào)用名稱還是使用模板函數(shù)時(shí)會(huì)存在轉(zhuǎn)發(fā)的錯(cuò)誤??聪旅娴拇a就理解了
void myfunc_test(void func(int)) {}
void myfunc(int a) {}
void myfunc(int a, int b) {}
template<typename T>
void dotest(T t){}
template<typename T>
void func(T&& t)
{
myfunc_test(std::forward<T>(t));
}
int main()
{
// func(myfunc);
// func(dotest);
myfunc_test(myfunc);
}
一般來(lái)說(shuō),傳遞函數(shù)做為函數(shù)參數(shù)時(shí),用函數(shù)指針的情況很多,但也應(yīng)該知道,也可以直接傳遞函數(shù)名稱而不是指針的情況來(lái)操作。上面的代碼就是這樣,就會(huì)出現(xiàn)本節(jié)的問(wèn)題。但是如果直接調(diào)用函數(shù)myfunc_test而非完美轉(zhuǎn)發(fā),則沒(méi)有問(wèn)題。同樣,函數(shù)模板也是如此,因?yàn)楹瘮?shù)模板畢竟不是一個(gè)實(shí)例,而是一組類(lèi)似實(shí)例的泛型。
想要解決這個(gè)問(wèn)題也很簡(jiǎn)單,依照著普通編譯成功的方式,采用指針的方式直接指明調(diào)用的函數(shù)即可,模板也同樣如此。
5.位字段
位域是由機(jī)器字的若干任意部分組成的(如32位int的第3至5個(gè)比特),但這樣的實(shí)體是無(wú)法直接取地址的。而fwd的形參是個(gè)引用,本質(zhì)上就是指針,所以也沒(méi)有辦法創(chuàng)建指向任意比特的指針。
解決方案:制作位域值的副本,并以該副本來(lái)調(diào)用轉(zhuǎn)發(fā)函數(shù)。
struct xxxxx
{
std::uint32_t v: 4,
I : 4,
D : 6,
E : 2,
t : 16;
//...
};
void test(std::uint16_t v) {}
template<typename T>
void func(T&& t)
{
test(std::forward<T>(t));
}
int main()
{
xxxxx ip = {};
test(ip.t); //調(diào)用void func(int)
//func(ip.t); //error,func形參是引用,由于位域是比特位組成。無(wú)法創(chuàng)建比特位的引用!
//解決方案:創(chuàng)建位域的副本,并傳給func
auto length = static_cast<std::uint16_t>(ip.t);
func(length);
}
5.實(shí)例講解
實(shí)例:C++標(biāo)準(zhǔn)中的std::invoke
template <typename _Callable, typename... _Types>
auto invoke(_Callable&& obj, _Types... argv)
{
return _Invoke<_Callable, _Types...>::_Call(
std::forward<_Callable>(obj),
std::forward<_Types>(argv)...);
}到此這篇關(guān)于C++之std::forward完美轉(zhuǎn)發(fā)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++ std::forward完美轉(zhuǎn)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中HTTP?代理服務(wù)器的設(shè)計(jì)與實(shí)現(xiàn)詳解
代理服務(wù)器,即允許一個(gè)網(wǎng)絡(luò)終端(一般為客戶端)通過(guò)這個(gè)服務(wù)與另一?個(gè)網(wǎng)絡(luò)終端(一般為服務(wù)器)進(jìn)行非直接的連接,下面我們就來(lái)看看如何使用C++設(shè)計(jì)與實(shí)現(xiàn)一個(gè)HTTP?代理服務(wù)器吧2024-01-01
QT使用Http協(xié)議通信的實(shí)現(xiàn)示例
使用QT進(jìn)行應(yīng)用開(kāi)發(fā)時(shí),有時(shí)候需要進(jìn)行客戶端和服務(wù)端的網(wǎng)絡(luò)通信,本文主要介紹了QT使用Http協(xié)議通信的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
c語(yǔ)言根據(jù)用戶輸入的出生年份并計(jì)算出當(dāng)前年齡
這篇文章主要介紹了c語(yǔ)言根據(jù)用戶輸入的出生年份并計(jì)算出當(dāng)前年齡,需要的朋友可以參考下2023-03-03
vscode實(shí)現(xiàn)本地代碼自動(dòng)同步到遠(yuǎn)程機(jī)器的步驟
這篇文章主要介紹了vscode實(shí)現(xiàn)本地代碼自動(dòng)同步到遠(yuǎn)程機(jī)器的步驟,本文分步驟給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06
C/C++之動(dòng)態(tài)內(nèi)存管理方式(new delete)
C++中new/delete集成內(nèi)存分配與對(duì)象構(gòu)造析構(gòu),是面向?qū)ο髢?nèi)存管理的核心,與malloc/free相比,支持類(lèi)型安全、異常處理及數(shù)組操作,需嚴(yán)格配對(duì)使用,現(xiàn)代開(kāi)發(fā)推薦智能指針替代,以提升安全性與自動(dòng)化程度2025-09-09
C++實(shí)現(xiàn)經(jīng)典24點(diǎn)紙牌益智游戲
這篇文章主要介紹了C++實(shí)現(xiàn)經(jīng)典24點(diǎn)紙牌益智游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03
C++進(jìn)一步認(rèn)識(shí)類(lèi)與對(duì)象
類(lèi)是創(chuàng)建對(duì)象的模板,一個(gè)類(lèi)可以創(chuàng)建多個(gè)對(duì)象,每個(gè)對(duì)象都是類(lèi)類(lèi)型的一個(gè)變量;創(chuàng)建對(duì)象的過(guò)程也叫類(lèi)的實(shí)例化。每個(gè)對(duì)象都是類(lèi)的一個(gè)具體實(shí)例(Instance),擁有類(lèi)的成員變量和成員函數(shù)2021-10-10
C++ std::condition_variable 條件變量用法解析
condition_variable(條件變量)是 C++11 中提供的一種多線程同步機(jī)制,它允許一個(gè)或多個(gè)線程等待另一個(gè)線程發(fā)出通知,以便能夠有效地進(jìn)行線程同步,這篇文章主要介紹了C++ std::condition_variable 條件變量用法,需要的朋友可以參考下2023-09-09
C語(yǔ)言實(shí)現(xiàn)讀取CSV文件的方法詳解
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言如何實(shí)現(xiàn)讀取CSV文件,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12
C語(yǔ)言矩陣連乘 (動(dòng)態(tài)規(guī)劃)詳解
這篇文章主要介紹了C語(yǔ)言矩陣連乘 (動(dòng)態(tài)規(guī)劃)詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05

