2010-03-26 92 views
29

閱讀http://www.cprogramming.com/tutorial/references.html,它說:爲什麼我不需要檢查引用是否無效/空?

一般情況下,引用應該始終 是有效的,因爲你必須始終 初始化一個參考。這意味着 ,除了一些奇怪的 的情況下(見下文),你可以是 確定使用參考只是 像使用普通的舊非參考 變量。您不需要檢查到 請確保參考不是 指向NULL,並且您將不會得到 由未初始化的引用 咬住,您忘記分配內存 。

我的問題是我怎麼知道對象的內存沒有被釋放/刪除後,你已經初始化的引用。

歸根結底,我不能對信仰採取這個建議,我需要更好的解釋。

任何人都可以點亮一下嗎?

+0

相關:http://stackoverflow.com/questions/1919608/checking-for-null-before-pointer-使用/ 1921403#1921403。 – 2010-03-26 16:50:07

+20

「我怎麼知道對象的內存在你初始化引用後沒有被釋放/刪除。」你怎麼知道某人沒有爲你的類型「reinterpet_cast」一些完全不正確的對象,並將其作爲參考傳遞給它?或者破壞了他們物體中的記憶,然後傳給你?在C和C++中,你不必依靠你的調用者來做一個muppet。如果他們做了一些語言定義爲無效的東西,比如解引用一個空指針或者刪除一個對象,但是保留對它的引用,那麼這就是他們的錯誤,他們可以調試它。 – 2010-03-26 16:52:50

回答

66

你可以不知道,如果引用是無效的:

有沒有辦法知道,如果你的參考引用有效的內存,除了通過採取你如何使用引用護理。例如,如果不確定何時刪除內存,則不希望使用堆中創建的某個引用。

您也永遠無法知道您使用的指針是否指向有效內存。

你可以做兩個指針和引用NULL檢查,但通常你永遠不會做一個參考NULL檢查,因爲沒有人會寫這樣的代碼:

int *p = 0; 
int &r = *p;//no one does this 
if(&r != 0)//and so no one does this kind of check 
{ 
} 

當使用參考?

你可能想在情況下使用引用這樣的:

//I want the function fn to not make a copy of cat and to use 
// the same memory of the object that was passed in 
void fn(Cat &cat) 
{ 
    //Do something with cat 
} 

//...main... 
Cat c; 
fn(c); 

在腳下拍攝你自己是很難用引用:

這是更難搬起石頭砸自己的與引用相比,它是指針。

例如:

int *p; 
if(true) 
{ 
    int x; 
    p = &x; 
} 

*p = 3;//runtime error 

你不能做這種以來的參考文獻的事情與它的價值必須被初始化。你只能使用你的範圍內的值來初始化它。

你仍然可以用引用來拍攝自己的腳,但你必須真的嘗試去做。

例如:

int *p = new int; 
*p = 3; 
int &r = *p; 
delete p; 
r = 3;//runtime error 
+1

非常好寫。 +1。 – 2010-03-26 16:57:57

+6

「你可能不想使用一個在堆上創建的東西的引用」?爲什麼不?如果我在堆上創建一個實例,通過引用一個方法傳遞它然後釋放它,我很好。唯一的問題是,如果方法存儲該引用並假定它將保持有效。 – 2010-03-26 17:15:09

+0

@Steven Sudit:當然有一些場景,所以這就是我爲什麼使用可能。至少我可以在我自己的代碼中說,如果我這樣做,那麼它是相對罕見的。 – 2010-03-26 17:20:08

2

你需要保持你的變量的理智 - 即,只是一個參考/指針傳遞給一些功能,如果你知道的功能範圍不會活得比你引用/指針。

如果你去釋放一些把手,然後嘗試使用上述基準你會讀free'd內存。

5

你不能。你也不能用指針。試想一下:



struct X 
{ 
    int * i; 
    void foo() { *i++; } 
}; 

int main() 
{ 
    int *i = new int(5); 
    X x = { i }; 
    delete i; 
    x.foo(); 
} 

現在,你可以把什麼樣的代碼在X :: foo的(),以確保我的指針仍然有效?

答案是沒有標準檢查。在調試模式下有一些技巧可以在msvc上工作(檢查0xfeeefeee或其他),但沒有任何工作會一直工作。

如果你需要某種形式的對象,它可以確保指針不指向已釋放的內存,你需要的東西比一個基準或標準指針更聰明。

這就是爲什麼你的指針和引用工作時必須相當不錯小心所有權語義和生命週期管理。

+0

同樣的問題擺在另一個迴應:如果當我刪除指針時,我將指針設置爲null後檢查爲空?這不是我可以檢查指針是否不再有效的方法嗎? – jbu 2010-03-26 16:35:34

+5

您始終可以擁有別名指針。說'p'和'q'都是指向同一個內存的指針。你刪除'p'並設置'p = NULL'。現在,'q'怎麼辦?這是無效的,也不是NULL。知道當你刪除一個指針的所有別名時,並不總是微不足道的。 – 2010-03-26 16:37:47

1

因爲當你到達那裏時,你已經確定了一個未定義的行爲。讓我來解釋一下:)

假設你有:

void fun(int& n); 

現在,如果你通過這樣的:

int* n = new int(5); 
fun(*n); // no problems until now! 

但是,如果你做到以下幾點:

int* n = new int(5); 
... 
delete n; 
... 
fun(*n); // passing a deleted memory! 

通過當你到達fun的時候,你將會解除引用* n,這是未定義的行爲,如果指針被刪除爲在上面的例子中。所以,沒有辦法,現在必須有現實的方法,因爲假設有效的參數是整個參考點。

+0

如果當我刪除指針時,我將指針設置爲null後檢查null?這不是我可以檢查指針是否不再有效的方法嗎? – jbu 2010-03-26 16:35:10

+0

@jbu:沒關係。如果你有一個對象的引用,並且它引用的對象消失了,你就有未定義的行爲。更好的辦法是實際解決問題,並確保你指的是不會從你下面掉下來的東西。 – GManNickG 2010-03-26 16:45:39

+1

@jbu:如果在刪除之後你將指針設置爲null,那麼在刪除之後,你*不能*引用指針來初始化一個引用。 – 2010-03-26 16:45:53

1

沒有語法來檢查引用是否有效。您可以測試NULL的指針,但是對於引用沒有有效/無效的測試。當然,被引用的對象可以被一些錯誤的代碼釋放或覆蓋。指針的情況也是如此:如果非NULL指針指向已發佈的對象,則不能對其進行測試。

+0

你不能測試對NULL的引用嗎?如果我做&myref == NULL怎麼辦? – codymanix 2010-03-26 16:38:02

+0

您可以測試null的參考。 – codenheim 2010-03-26 16:44:06

+0

@mrjoltcola:不,你不能。 @codymanix的建議是未定義的,因爲空引用的存在是未定義的。 – jalf 2010-03-26 16:48:25

1

簡而言之,它可能發生 - 但如果它發生,你有一個嚴重的設計問題。你也沒有真正的方法來檢測它。答案是設計你的程序來防止它發生,而不是試圖建立某種不會真正起作用的檢查(因爲它不能)。

3

在C++中,將參考主要是爲了被用作參數和返回類型的功能。在參數的情況下,由於函數調用的性質,引用不能引用不再存在的對象(假設單個線程化程序)。在返回值的情況下,應該限制自己返回生命期比函數調用時間更長的類成員變量,或者傳遞給函數的引用參數。

+0

除非單線程程序碰巧刪除引用的地址,它知道它在堆上。一個人不會這樣做,但它可能:) – 2010-03-26 16:43:24

+0

尼爾的答案是正確的。 – 2010-03-26 17:25:41

+0

「假設單線程程序」。嗯? 'void f(int&r,int * p){delete p; r + = 1; } int main(){int * a = new int(); f(* a,a); }'。創建一個懸而未決的參考。我沒有看到程序的單線程或多線程與什麼有關。即使只考慮自動化作爲參考,一旦有人有參考,他們可以將其存儲在一個對象中(通過構造函數),存儲一個指向該對象的指針(全局),以便逃避該對象的範圍,並通過它稍後作爲函數參數。 UB當然是如此「不可能發生」,但與fn呼叫的性質無關。 – 2010-03-26 18:23:43

0

我認爲它「依賴」。我知道,這不是一個答案,但它確實取決於。我認爲防守編碼是一種很好的做法。現在,如果您的堆棧軌道深度達到10級,並且任何跟蹤失敗導致整個操作失敗,那麼通過一切手段檢查頂級,並讓任何例外都升至頂級。但是如果您可以從通過您的某個空引用中恢復,則在適當的情況下進行檢查。根據我的經驗,在我必須將代碼與其他公司集成在一起時,在公共api級別檢查(和記錄)所有內容可以讓您在集成未按預期發生時發生偏移。

1

C++引用是別名。這樣做的效果是,對指針的解引用不一定發生在它們出現的地方,它們發生在它們被評估的地方。對一個對象的引用不會評估該對象,它會對其進行別名。使用參考是評估對象的東西。 C++不能保證引用是有效的;如果是這樣,所有的C++編譯器都會中斷。這樣做的唯一方法是通過引用消除動態分配的所有可能性。在實踐中,假設是一個引用是一個有效的對象。由於* NULL未定義爲&無效,因此對於p = NULL,* p也未定義。 C++的問題是* p將被有效地傳遞給一個函數,或者延遲到它的評估,直到實際使用該引用爲止。認爲它是未定義的不是提問者問題的要點。如果它是非法的,編譯器會強制執行它,標準也是如此。我也沒有意識到這一點。

int &r = *j; // aliases *j, but does not evaluate j 
if(r > 0) // evaluates r ==> *j, resulting in dereference (evaluate j) at this line, not what some expect 
    ; 

1)可以測試混疊NULL指針的引用,& r是簡單地&(無論ř別名)(編輯)

2)當通過一個 「引用」 指針(* I )作爲參考參數,取消引用不會發生在調用網站,它可能永遠不會發生,因爲它是引用(引用是別名,而不是評估)。這是參考文獻的優化。如果它們在調用點進行評估,編譯器會插入額外的代碼,或者它會是一個按值調用,性能比指針低的調用。

是的,引用本身不是NULL,它是無效的,就像* NULL是無效的。這是推遲評估解引用表達式與聲明不可能具有無效引用不一致的原因。

#include <iostream> 

int fun(int & i) { 
    std::cerr << &i << "\n"; 
    std::cerr << i << "\n"; // crash here 
} 

int main() { 
    int * i = new int(); 
    i = 0; 
    fun(*i); // Why not crash here? Because the deref doesn't happen here, inconsistent, but critical for performance of references 
} 

編輯:改變了,因爲它已經被誤解爲建議用於測試的參考,不是我想證明我的例子。我只想證明無效的參考。

+0

你的「* i」是未定義的行爲...也許它會起作用,也許它會後者崩潰,或者可能伸手背在背後,用舊襪子掐死你... – 2010-03-26 17:20:31

+0

未定義的行爲意味着規範不會作出判斷。關鍵是它是我知道的每個編譯器中的合法行爲,併發生在實際代碼中,這些代碼將指針與使用引用參數的庫混合。 – codenheim 2010-03-26 17:26:44

+0

「某些答案」正在討論標準定義的內容。某些實現要麼保證UB特定情況下的某些行爲,要麼不保證它,但是當前做了可預測的事情,並不意味着答案是錯誤的。這只是描述一個接口和描述該接口的一個或多個特定實現的區別。 – 2010-03-26 18:33:03

4

我的問題是我怎麼知道對象的內存沒有被釋放/刪除後,你已經初始化的引用。

首先,有從未任何方式,如果一個內存位置已被釋放/刪除檢測。這與它是否爲空無關。指針也是如此。給定一個指針,你無法確保它指向有效的內存。你可以測試一個指針是否爲空,但就是這樣。一個非空指針可能仍然指向釋放內存。或者它可能指向一些垃圾位置。

至於引用,這同樣適用於你無法確定它是否引用仍然有效的對象。但是,在C++中沒有這樣的「空引用」,因此不需要檢查引用是否爲「null」。

當然,可以編寫代碼來創建看起來像「空引用」的代碼,並且該代碼將被編譯。但它不會是正確。根據C++語言標準,不能創建對null的引用。試圖這樣做是未定義的行爲。

什麼它歸結爲是我不能接受的信仰這個建議,我需要一個更好的解釋

更好的解釋是這樣的:「一個參考點,以一個有效的對象,因爲將它設置爲指向一個有效的對象「。你不必信賴它。你只需要看看你創建引用的代碼。它當時指向一個有效的對象,或者它沒有。如果沒有,那麼代碼是不正確的,應該改變。

而且該引用仍然有效,因爲您知道它將被使用,因此您確保不會使其引用的對象無效。

這真的很簡單。只要您不摧毀它們指向的對象,引用就會保持有效。所以不要摧毀它指向的對象,直到不再需要引用爲止。

+0

@jalf:在我看來,忽略基於「未定義」的功能並不能幫助提問者。我們都知道「有趣(\ * i)」會發生。任何帶參考參數的函數都可能接收到一個「空引用」,直到在被調用者中評估纔會被找到。這是不同的。是的,你應該在通話之前檢查指針,我同意。但是,如果C++ _really_保證了狀態,那麼在函數調用之前,必須檢查表達式「* i」處的指針取消引用。請將我指向涵蓋此的標準。 – codenheim 2010-03-26 17:19:21

+2

@mrjoltcola:當C++「保證」任何事情時,任何事情都是如此,該保證取決於程序的格式良好並且不會引發未定義的行爲。如果C++沒有「真的保證」引用是非空的,那麼它也不會「確保」1 + 1 == 2。一旦未定義的行爲被調用,其中一方或雙方都可能失敗。 1.3.12表示「完全忽略不可預知的結果」是允許的。 – 2010-03-26 18:11:43

+0

你說服了我,但那些說「不可能發生」的答案是錯誤的,這就是促使我回答的原因。 「不可能發生」是錯誤的選擇。我想每個人都誤解了我的觀點。 – codenheim 2010-03-26 18:37:22

0

我想你可以從一個簡單的並行機制:

  • T &類似於T * const
  • T const &類似於T const * const

的參考資料,他們的意圖非常相似const,他們攜帶一個含義,從而幫助編寫更清晰的代碼,但不提供不同的運行時行爲。

現在回答你的問題:是的,可能是引用爲空或無效。您可以測試空引用(T& t = ; if (&t == 0)),但不應該發生>>按合約引用有效。

何時使用引用vs指針?

  • 你希望能夠改變指針對象
  • 你想表達的可能無效

在任何其他情況下,使用一個參考:如果用一個指針。

一些例子:

// Returns an object corresponding to the criteria 
// or a special value if it cannot be found 
Object* find(...); // returns 0 if fails 

// Returns an object corresponding to the criteria 
// or throw "NotFound" if it cannot be found 
Object& find(...); // throw NotFound 

傳遞參數:

void doSomething(Object* obj) 
{ 
    if (obj) obj->doSomething(); 
} 

void doSomething(Object& obj) { obj.do(); obj.something(); } 

屬性:

struct Foo 
{ 
    int i; 
    Bar* b; // No constructor, we need to initialize later on 
}; 

class Foo 
{ 
public: 
    Foo(int i, Bar& b): i(i), b(b) {} 
private: 
    int i; 
    Bar& b; // will always point to the same object, Foo not Default Constructible 
}; 

class Other 
{ 
public: 
    Other(Bar& b): b(&b) {} // NEED to pass a valid object for init 

    void swap(Other& rhs); // NEED a pointer to be able to exchange 

private: 
    Bar* b; 
}; 

功能引用和指針起到同樣的作用。這只是一個合同問題。不幸的是,如果你刪除了它們引用的對象,它們都可以調用未定義的行爲,這裏沒有贏家;)