簡體   English   中英

C ++參考 - 它們只是語法糖嗎?

[英]C++ references - are they just syntactic sugar?

C ++參考只是語法糖,還是在某些情況下提供任何加速?

例如,無論如何,按指針調用都涉及到一個副本,並且對於逐個引用調用似乎也是如此。 潛在的機制似乎是相同的。

編輯:經過大約六個答案和許多評論。 我仍然認為參考文獻只是合成糖。 如果人們可以直接回答是或否,如果有人可以接受答案?

假設引用為指針:

  1. 不能為NULL
  2. 一旦初始化,就無法重新指向其他對象
  3. 任何使用它的嘗試都會隱含地取消引用它:

     int a = 5; int &ra = a; int *pa = &a; ra = 6; (*pa) = 6; 

這里看起來像反匯編:

    int a = 5;
00ED534E  mov         dword ptr [a],5  
    int &ra = a;
00ED5355  lea         eax,[a]  
00ED5358  mov         dword ptr [ra],eax  
    int *pa = &a;
00ED535B  lea         eax,[a]  
00ED535E  mov         dword ptr [pa],eax  

    ra = 6;
00ED5361  mov         eax,dword ptr [ra]  
00ED5364  mov         dword ptr [eax],6  

    (*pa) = 6;
00ED536A  mov         eax,dword ptr [pa]  
00ED536D  mov         dword ptr [eax],6  

從編譯器的角度來看,分配給引用與分配給解除引用的指針是一回事。 正如您所看到的,它們之間沒有區別(我們現在不討論編譯器優化)但是如上所述,引用不能為空並且對它們包含的內容有更強的保證。

至於我,我更喜歡使用引用,只要我不需要nullptr作為有效值,應該重新定義的值或要傳遞給不同類型的值(例如指向接口類型的指針)。

引用具有比指針更強的保證,因此編譯器可以更積極地進行優化。 我最近看到GCC完全通過函數引用內聯多個嵌套調用,但不是通過函數指針的單個嵌套調用(因為它無法證明指針總是指向同一個函數)。

如果引用最終存儲在某處,則它通常占用與指針相同的空間。 這並不是說,它將像指針一樣被使用:如果編譯器知道引用綁定到哪個對象,編譯器可能會很好地通過它。

編譯器不能假設指針是非null的; 在優化代碼時,它必須要么證明指針是非空的,要么發出一個程序來解釋它為空的可能性(在明確定義的上下文中)。

同樣,編譯器也不能假設指針永遠不會改變值。 (它也不能假設指針指向一個有效的對象,雖然我很難想象一個在明確定義的上下文中這很重要的情況)

在另一方面,假設引用,作為指針實現,編譯仍然可以假設它是不為空,永遠不會改變它指向,並指向一個有效的對象。

引用與指針的不同之處在於,您無法對引用執行某些操作並將其定義為行為。

您不能獲取引用的地址,而只能獲取引用的地址。 創建后,您無法修改引用。

一個T&和一個T*const (注意const適用於指針,而不是指向那里),它們相對類似。 獲取實際const值的地址並對其進行修改是未定義的行為,因為修改(它直接使用的任何存儲)引用。

現在,在實踐中,您可以獲得一個參考存儲:

struct foo {
  int& x;
};

sizeof(foo)幾乎肯定等於sizeof(int*) 但編譯器可以自由地忽略直接訪問foo字節的人實際可能改變所引用的值的可能性。 這允許編譯器一次讀取引用“指針”實現,然后再也不讀它。 如果我們有struct foo{ int* x; } struct foo{ int* x; }編譯器將不得不每次它做了一個證明*fx指針的值沒有改變。

如果你有struct foo{ int*const x; } struct foo{ int*const x; }再次啟動在其不可改變的表現的參考樣(修改被宣布一些const是UB)。


我不知道任何編譯器編寫者使用的技巧是在lambda中壓縮引用捕獲。

如果你有一個lambda通過引用捕獲數據,而不是通過指針捕獲每個值,它只能捕獲堆棧幀指針。 每個局部變量的偏移量是堆棧幀指針之外的編譯時常量。

例外是通過引用捕獲的引用,即使引用變量超出范圍,在C ++的缺陷報告下也必須保持有效。 所以那些必須由偽指針捕獲。

一個具體的例子(如果玩具一個):

void part( std::vector<int>& v, int left, int right ) {
  std::function<bool(int)> op = [&](int y){return y<left && y>right;};
  std::partition( begin(v), end(v), op );
}

上面的拉姆達可以捕獲僅堆棧幀指針,知道leftright是相對於它,減少它的尺寸,而不是捕獲2個int S按(基本上指針)參考。

這里我們有[&]隱含的引用,它們的存在比它們通過值捕獲的指針更容易被刪除:

void part( std::vector<int>& v, int left, int right ) {
  int* pleft=&left;
  int* pright=&right;
  std::function<bool(int)> op = [=](int y){return y<*pleft && y>*pright;};
  std::partition( begin(v), end(v), op );
}

引用和指針之間還有一些其他差異。

引用可以延長臨時的生命周期。

這在for(:)循環中大量使用。 for(:)循環的定義都依賴於引用生命周期擴展以避免不必要的副本,而for(:)循環的用戶可以使用auto&&自動推導出最輕的權重方式來包裝迭代對象。

struct big { int data[1<<10]; };

std::array<big, 100> arr;

arr get_arr();

for (auto&& b : get_arr()) {
}

這里引用生命周期擴展小心地防止不必要的副本發生。 如果我們改變make_arr以返回一個arr const&並且它繼續工作而沒有任何副本。 如果我們更改get_arr以返回一個按值返回big元素的容器(例如,輸入迭代器范圍),則不會再進行任何不必要的復制。

這在某種意義上是語法糖,但它允許相同的構造在許多情況下是最佳的,而不必根據事物的返回或迭代進行微觀優化。


類似地,轉發引用允許數據被智能地視為const,非const,左值或右值。 臨時工具被標記為臨時工具,用戶不再需要的數據被標記為臨時數據,將持久存儲的數據標記為左值參考。

這里的優勢引用超過了非引用,你可以形成一個臨時的rvalue引用,並且你不能形成一個指向那個臨時的指針而不通過rvalue引用到左值引用轉換。

沒有


引用不僅僅是語法差異; 它們也有不同的語義

  • 引用總是別名現有對象,不像指針可能是nullptr (sentinel值)。
  • 引用無法重新定位,它始終指向同一個對象。
  • 引用可以延長對象的生命周期,請參閱綁定到auto const&auto&&

因此,在語言層面,引用是它自己的實體。 其余的是實施細節。

過去有效率優勢,因為編譯器更容易參考優化。 然而,現代編譯器已經變得如此優秀,以至於不再有任何優勢。

超指針的一個巨大優勢參考是引用可以引用寄存器中的值,而指針只能指向內存塊。 獲取寄存器中的某些內容的地址,然后強制編譯器將該值放入普通的內存位置。 這可以在緊密循環中創造巨大的好處。

但是,現代編譯器非常好,以至於它們現在可以識別出一個指針,它可以作為所有意圖和目的的參考,並將其視為與引用完全相同。 這可能會在調試器中產生相當有趣的結果,您可以在其中使用諸如int* p = &x類的語句,要求調試器打印p的值,只是讓它說出“p無法打印”的內容。因為x實際上是在寄存器中,並且編譯器將*p視為對x的引用! 在這種情況下, p實際上沒有價值

(但是,如果你試圖在p上進行指針運算,那么你就會強制編譯器不再優化指針,就像引用一樣,並且一切都會變慢)

8.3.2參考文獻[dcl.ref]

引用可以被認為是對象的名稱

這與保存Object **的內存位置地址的變量(不同於引用)的指針不同。 此變量的類型是指向Object的指針。

內部引用可以實現為指針,但標准永遠不會保證。

所以回答你的問題:C ++ Reference不是指針的語法糖。 它是否提供任何加速已經得到了深入的回答。

******這里的對象是指任何具有內存地址的實例。 甚至指針都是對象,因此函數也是如此(因此我們有嵌套指針和函數指針)。 在類似的意義上,我們沒有指向引用,因為它們沒有實例化。

暫無
暫無

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

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