[英]Are there any use cases for std::forward with a prvalue?
std::forward
的最常見用法是,完美轉發轉發(通用)引用,例如
template<typename T>
void f(T&& param)
{
g(std::forward<T>(param)); // perfect forward to g
}
這里param
是一個lvalue
, std::forward
最終將它轉換為rvalue或lvalue,具體取決於與它綁定的參數。
從cppreference.com看到std::forward
的定義我發現還有一個rvalue
重載
template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );
任何人都可以給我任何理由為什么rvalue
超載? 我看不到任何用例。 如果要將rvalue傳遞給函數,可以按原樣傳遞它,不需要在其上應用std::forward
。
這與std::move
不同,我在這里看到為什么還要一個rvalue
重載:你可能會處理通用代碼,在這些代碼中你不知道你傳遞了什么,並且你想要無條件支持移動語義,參見eg 為什么std :: move采用通用引用? 。
好的,因為@vsoftco要求簡潔的用例,這里是一個精致的版本(使用他的想法讓“my_forward”真正看到過載被調用)。
我通過提供一個代碼示例來解釋“用例”,該代碼示例沒有prvalue無法編譯或行為不同(無論這樣做是否真的有用)。
我們有std::forward
2次重載
#include <iostream>
template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type& t) noexcept
{
std::cout<<"overload 1"<<std::endl;
return static_cast<T&&>(t);
}
template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type&& t) noexcept
{
std::cout<<"overload 2"<<std::endl;
static_assert(!std::is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
我們有4個可能的用例
用例1
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &&
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(v)); // &
return 0;
}
用例2
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &&
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(std::move(v))); //&&
return 0;
}
用例3
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &
Library( vector<int> a):b(a){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(v)); // &
return 0;
}
用例4
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &
Library( vector<int> a):b(a){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(std::move(v))); //&&
return 0;
}
這是簡歷
請注意,如果我們不使用前進
Library a( std::move(v));
//and
Library a( v);
你得到:
如你所見,如果你只使用兩個forward
重載中的一個,你基本上導致不能編譯4個案例中的2個,而如果你根本不使用forward
,你將只能編譯4個案例中的3個。
這個答案是為了回答@vsoftco的評論
@DarioOO感謝您的鏈接。 你能寫一個簡潔的答案嗎? 從你的例子來看,我仍然不清楚為什么還需要為rvalues定義std :: forward
簡而言之:
因為沒有rvalue專門化,以下代碼將無法編譯
#include <utility>
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// hi! only rvalue here :)
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v));
return 0;
}
但是我無法拒絕輸入更多,所以這里的答案也不是簡單的版本。
長版:
您需要移動v
因為Library
沒有構造函數接受左值,但只有rvalue引用。 如果沒有完美的轉發,我們最終會出現不良行為:
傳遞重物時,包裹功能會產生高性能的陰謀。
使用移動語義,我們確保使用移動構造函數,如果可能的話。 在上面的例子中,如果我們刪除std::forward
,代碼將無法編譯。
那么,什么是真正在做forward
? 沒有我們的共識,移動元素? 不!
它只是創建矢量的副本並移動它。 我們怎么能確定呢? 只需嘗試訪問該元素。
vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v)); //what happens here? make a copy and move
std::cout<<v[0]; // OK! std::forward just "adapted" our vector
如果您改為移動該元素
vector<int> v;
v.push_back(1);
A a( std::move(v)); //what happens here? just moved
std::cout<<v[0]; // OUCH! out of bounds exception
因此需要重載才能實現仍然安全的隱式轉換,但如果沒有重載則不可能。
事實上,以下代碼將無法編譯:
vector<int> v;
v.push_back(1);
A a( v); //try to copy, but not find a lvalue constructor
真實用例:
您可能會爭辯說轉發參數可能會創建無用的副本,從而隱藏可能的性能損失,是的,這實際上是正確的,但請考慮實際用例:
template< typename Impl, typename... SmartPointers>
static std::shared_ptr<void>
instancesFactoryFunction( priv::Context * ctx){
return std::static_pointer_cast<void>( std::make_shared<Impl>(
std::forward< typename SmartPointers::pointerType>(
SmartPointers::resolve(ctx))...
) );
}
代碼取自我的框架(第80行): Infectorpp 2
在這種情況下,參數從函數調用轉發。 無論Impl
構造函數接受rvalue還是左值(因此沒有編譯錯誤和那些都被移動), SmartPointers::resolve
的返回值都被正確移動。
基本上你可以在任何情況下使用std::foward
,你想讓代碼更簡單,更可讀,但你必須記住2點
如果小心使用是一個強大的工具。
我之前盯着這個問題,讀過Howard Hinnant的鏈接,經過一個小時的思考后無法完全理解它。 現在我正在尋找並在五分鍾內得到答案。 (編輯:得到答案太慷慨了,因為Hinnant的鏈接有答案。我的意思是我理解,並且能夠以更簡單的方式解釋它,希望有人會發現有用的)。
基本上,這允許您在某些情況下是通用的,具體取決於傳入的類型。請考慮以下代碼:
#include <utility>
#include <vector>
#include <iostream>
using namespace std;
class GoodBye
{
double b;
public:
GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
GoodBye( const double& a):b(a){ std::cerr << "copy"; }
};
struct Hello {
double m_x;
double & get() { return m_x; }
};
int main()
{
Hello h;
GoodBye a(std::forward<double>(std::move(h).get()));
return 0;
}
此代碼打印“移動”。 有趣的是,如果我刪除std::forward
,它會打印副本。 對我來說,這很難讓我全神貫注,但讓我們接受並繼續前進。 (編輯:我想這會發生,因為get會返回一個左值引用到rvalue。這樣的實體衰變成一個左值,但是std :: forward會把它變成一個右值,就像常用的forward一樣。仍覺得不直觀雖然)。
現在,讓我們想象另一個類:
struct Hello2 {
double m_x;
double & get() & { return m_x; }
double && get() && { return std::move(m_x); }
};
假設在main
的代碼中, h
是Hello2的一個實例。 現在,我們不再需要std :: forward,因為對std::move(h).get()
的調用返回一個rvalue。 但是,假設代碼是通用的:
template <class T>
void func(T && h) {
GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}
現在,當我們調用func
,我們希望它與Hello
和Hello2
一起正常工作,即我們想觸發一個移動。 如果我們包含外部std::forward
,那只會發生對於Hello
的右值,所以我們需要它。 但是......我們得到了一個妙語。 當我們將Hello2
的rvalue傳遞給this函數時,get()的rvalue重載將返回一個rvalue double,因此std::forward
實際上接受了一個rvalue。 因此,如果沒有,您將無法像上面那樣編寫完全通用的代碼。
該死的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.