簡體   English   中英

是否有使用prvalue的std :: forward的用例?

[英]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是一個lvaluestd::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采用通用引用?

編輯為了澄清這個問題,我問為什么從這里需要重載(2) ,以及它的用例。

好的,因為@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;
}

這是簡歷

  1. 使用了重載1,沒有它就會出現編譯錯誤
  2. 使用了重載2,沒有它就會出現編譯錯誤
  3. 使用重載1,但是沒有編譯錯誤
  4. 使用了重載2,沒有它就會出現編譯錯誤

請注意,如果我們不使用前進

Library a( std::move(v));
//and
Library a( v);

你得到:

  1. 編譯錯誤

如你所見,如果你只使用兩個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點

  • 額外的編譯時間(實際上不是那么多)
  • 可能會導致不需要的副本(當您沒有明確地將某些內容移動到需要rvalue的內容時)

如果小心使用是一個強大的工具。

我之前盯着這個問題,讀過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 ,我們希望它與HelloHello2一起正常工作,即我們想觸發一個移動。 如果我們包含外部std::forward ,那只會發生對於Hello的右值,所以我們需要它。 但是......我們得到了一個妙語。 當我們將Hello2的rvalue傳遞給this函數時,get()的rvalue重載將返回一個rvalue double,因此std::forward實際上接受了一個rvalue。 因此,如果沒有,您將無法像上面那樣編寫完全通用的代碼。

該死的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM