2009-09-07 104 views
8

爲什麼下面的代碼打印「xxY」?本地變量是否應該存在於整個函數的範圍之內?我可以使用這種行爲嗎?或者這將在未來的C++標準中改變?局部變量範圍問題

我認爲根據C++標準3.3.2「在一個塊中聲明的名稱是局部的塊,其電勢範圍起始於它的點聲明的,並且在其聲明區域的結尾處結束。

#include <iostream> 
using namespace std; 

class MyClass 
{ 
public: 
    MyClass(int) { cout << "x" << endl; }; 
    ~MyClass() { cout << "x" << endl; }; 
}; 

int main(int argc,char* argv[]) 
{ 
    MyClass (12345); 
// changing it to the following will change the behavior 
//MyClass m(12345); 
    cout << "Y" << endl; 

    return 0; 
} 

基於該響應我可以假設MyClass(12345);是表達式(和範圍)。這是有道理的。因此,我希望下面的代碼將打印「XY-X」總是:

MyClass (12345), cout << "Y" << endl; 

並且允許做出這樣的替換:

// this much strings with explicit scope 
{ 
    boost::scoped_lock lock(my_mutex); 
    int x = some_func(); // should be protected in multi-threaded program 
} 
// mutex released here 

//  

// I can replace with the following one string: 
int x = boost::scoped_lock (my_mutex), some_func(); // still multi-thread safe 
// mutex released here 
+2

你的問題包含答案:*名稱聲明... *。沒有名字! – quamrana 2009-09-07 10:52:09

+0

在這個例子中:MyClass(12345)是一個函數式樣轉換,而不是一個聲明。 – 2009-09-07 10:54:21

+0

仍然沒有實例的名稱 – artificialidiot 2009-09-07 11:04:29

回答

4

你正確地引用標準。我要強調:

一個宣佈在一個塊是局部的塊。其潛在範圍始於宣告地點,並在宣告地區結束時結束。

你沒有宣佈任何,其實。你

MyClass (12345); 

甚至不包含一項聲明!它包含的是一個創建MyClass實例的表達式,計算表達式(然而,在這種特殊情況下沒有什麼可計算的),並將結果轉換爲void,並銷燬在那裏創建的對象。

一個不太令人困惑的事情會聽起來像是

call_a_function(MyClass(12345)); 

你看到了很多次,知道它是如何工作的,不是嗎?

+0

規格還討論了「潛在」範圍。不「保證」。因此,如果編譯器未在範圍內使用,它可以在早期銷燬該對象。 – 2009-09-07 10:55:20

+4

「潛在作用域」具有確切含義:它是作用域加上由於重新聲明而隱藏名稱的部分。即使不再使用它,實現也不能破壞對象(這會破壞RAII btw的一些主要用途)。 – AProgrammer 2009-09-07 11:18:43

8

你實際上是在創建一個對象,而保持它的範圍,所以它在創建後立即銷燬。因此,你正在經歷的行爲。

您不能訪問所創建的對象那麼,爲什麼編譯器保持它周圍?

16

MyClass(12345); 

創建的對象是臨時對象,其是僅在該表達活着;

MyClass m(12345); 

是一個對象,它是活着的整個塊。

+0

是否*表達*是主要功能? – 2009-09-07 10:43:11

+1

這似乎對我來說。另一件事可能是優化:即使你使用第二種方法,編譯器可能會優化它到第一種。 – Anna 2009-09-07 10:43:17

+0

@安娜,在第二種情況下,它總是會打印xYx。 – 2009-09-07 10:44:05

5

爲了回答您的其他問題。以下是逗號運算符的調用。它創建一個MyClass臨時表,其中包括調用其構造函數。然後評估第二個表達式cout << "Y" << endl,它將打印出Y.然後,在完整表達式的末尾,將銷燬臨時的,它將調用它的析構函數。所以你的期望是正確的。

MyClass (12345), cout << "Y" << endl; 

對於以下工作,您應該添加括號,因爲逗號在聲明中具有預定義的含義。它會開始聲明函數some_func返回int並且不帶參數,並將scoped_lock對象分配給x。使用括號,你說整個事情是一個單一的逗號運算符表達式。

int x = (boost::scoped_lock (my_mutex), some_func()); // still multi-thread safe 

應當注意的是,下面的兩行是等價的。第一個是而不是使用my_mutex作爲構造函數參數創建臨時未命名對象,但是名稱周圍的括號是多餘的。不要讓語法混淆你。

boost::scoped_lock(my_mutex); 
boost::scoped_lock my_mutex; 

我見過的條款範圍和壽命的濫用。

  • Scope是您可以在不限定名稱的情況下引用名稱的地方。名稱具有範圍,並且對象繼承用於定義它們的名稱範圍(因此標準有時稱爲「本地對象」)。臨時對象沒有範圍,因爲它沒有名稱。同樣,由new創建的對象沒有範圍。範圍是一個編譯時間屬性。這個術語在標準中經常被濫用,見this defect report,所以找到一個真正的意義是相當混亂的。

  • Lifetime是一個運行屬性。這意味着對象何時被設置並準備好使用。對於類類型的對象,生命期在構造函數結束執行時開始,並在析構函數開始執行時結束。生活經常與範圍混淆,儘管這兩件事情完全不同。

    臨時的生命週期是精確定義的。他們中的大多數在評估它們包含的完整表達式後(例如,上面的逗號操作符或賦值表達式),它們會終止生命週期。臨時可以綁定到會延長其壽命的常量引用。拋出異常的對象也是臨時對象,當它們不再有處理程序時,它們的生命週期結束。