2011-08-16 139 views
1

我讀上GTEST介紹,發現這部分困惑:爲什麼這是「無效的C++」

編譯器抱怨「未定義的引用」到一些靜態 const成員變量,但我並定義它們在課堂上。 有什麼問題?

如果你的類有一個靜態數據成員:

// foo.h 
class Foo { 
    ... 
    static const int kBar = 100; 
}; 

您還需要定義它的類體之外foo.cc中:

const int Foo::kBar; // No initializer here. 

否則你的代碼是無效C++,並可能會突破 的方式。特別是在Google Test比較聲明 (EXPECT_EQ等)中使用它會生成一個「未定義參考」鏈接器錯誤。

有人可以解釋爲什麼在類中定義一個靜態常量而不在類體外定義它是非法的C++嗎?

+0

類體中的明顯定義不是形式上的*定義*,它只是一個*聲明*。它只能使用該值,而不能用於使用該對象。 C++ 98標準在這裏(在其ODR中)有問題,關於什麼時候被「使用」了,但是它大部分都是在C++ 0x中修復的。 –

+0

如果會有任何對它的引用,你需要在源文件(.cpp)中轉發聲明靜態變量。 – AJG85

回答

5

首先第一件事,在一個類體內不是一個定義,它是一個聲明。該聲明指定了常量的類型和值,該定義保留了存儲空間。您可能不需要存儲空間,例如,如果只將該值用作編譯時間常量。在這種情況下,你的代碼是完全合法的C++。但是,如果你按照引用傳遞常量,或者將指針指向常量,那麼你也需要存儲。在這些情況下,你會得到一個'未定義的參考'錯誤。

+0

「該定義指定了常量的類型和值,該定義保留了存儲空間。」typo? – NoSenseEtAl

+0

@NoSenseEtAl:是的,已修復 – john

3

該標準基本上規定,即使您可以在標題中給出一個值,如果靜態變量是「已使用」,您仍然需要將其定義在源文件中。

在此上下文中,「used」通常被理解爲表示程序的某些部分需要實際的存儲器和/或變量的地址。

最有可能的是谷歌測試代碼在某個時候獲取變量的地址(或以某種其他等效的方式使用它)。

+0

某些編譯器是否讓您無需執行它?我不記得曾經在源文件的類之外再添加一個聲明,我從來沒有得到任何錯誤(在MSVC++中)。 –

+1

@Seth Carnegie:這一切都取決於你是否「使用」了價值。有趣的是,今天早些時候,我有點失望。代碼'struct test {static const int x = 10; }; void foo(int const&x){std :: cout << x; } int main(){std :: cout << test :: x; foo(test :: x); }'顯示了靜態常量的兩種不同用法。在表達式'cout << test :: x'中,重載決議發現'ostream :: operator <<(int)'是最好的重載,並且只需要一個右值,因此只有聲明和初始化是好的。另一方面,調用'foo'需要一個左值(綁定到引用)。 –

+1

...綁定你需要一個左值的引用,這意味着靜態成員根據標準被*使用*,並且它被使用的事實意味着定義是必需的。如果您想到它,參考*的綁定需要*成員的位置,並且該位置需要定義。在右值情況下,地址不是必需的,只有*值*,並且編譯器可以將常量注入到位(即在上例中,編譯器將用'10'替換'test :: x') –

2

粗略地說:在類定義中,static const int kBar = 100;告訴編譯器「Foo將有一個kBar常量(我保證它總是100)」。但是,編譯器不知道該變量的位置。在foo.cc文件中,const int Foo::kBar;告訴編譯器「好的,在這個位置做kBar」。否則,鏈接器會尋找kBar,但無法在任何地方找到它。