2015年1月21日

[C++] static const Member in a struct/class

C/C++ 的 struct/class (為方便解說,接下來統一用 struct ) 可以將其成員變數的型態附加上 static const 修飾詞,如此一來該成員變數即為所有該 structure 產生出來的 object 共享的成員變數,並且也無須透過特定的 object 來存取,可以直接用 structure name 去存取該成員變數,例如:

struct A
{
    static const int val = 1;
};
int x = A::val + 1; // direct access

一般來說雖然不允許 structure 的成員變數直接在宣告時給予初始化,但是有 static const 修飾的成員變數是唯一的例外,允許直接在宣告時就給予初始值。不過這次這種使用方法卻讓我遇上不明究理的 link error,簡述如下:


[fileA.h]
struct A
{
    static const int val = 1;
};

[fileB.cpp]
std::unordered_map<int, std::string> umap;
/* some code here */
std::string str = umap.at(A::val) + "123"; // error here

我註解的地方就是 link error 所在,那邊出錯的原因是這個:

undefined reference to A::val

但是如果我把 code 改成這樣就沒問題了:

[fileB.cpp]
std::unordered_map<int, std::string> umap;
/* some code here */
const int VAL = A::val;
std::string str = umap.at(VAL) + "123"; // pass

因為想不出來為什麼這樣會 fail,所以去 PTT 的 C_and_CPP 版發問後得到的結果是這樣:

在 C++11 的標準中的 9.4.2.3 有這麼一段:

If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression (5.19). A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. 
[ Note: In both these cases, the member may appear in constant expressions. — end note ] 
The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

問題就在紅色字體的那一段:在我原先的做法中,如果成員變數有被 static const 修飾,我不會另外再定義一次,所以使用時這個成員變數的實體可能不存在,也就 link error 囉。雖然以前沒遇上問題,但是多半是 compiler 很聰明 & 運氣好沒遇上。

正確的做法應該是要多宣告這一行來確保 compiler 會產生該成員變數的實體存在

const int A::val;

如此一來就不會讓 linker 在 link 時找不到成員變數的實體了

沒有留言:

張貼留言