C++在Class內定義常數(constant)的方法

要在class之中定義常數會遇到如何初始化該常數的問題。
C++11以前的標準不允許在class definition中初始化members。

嘗試直接在class definition中初始化變數

1
2
3
4
5
6
7
8
9
10
#include <string>
#include <iostream>
class Greeting {
public:
const std::string message = "Hello!";
void greet() {
std::cout << message << std::endl;
}
}

compile以上snippet會產生錯誤:

1
2
3
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:31:warning:</span> non-static data member initializers only available with -std=c++11 or -std=gnu++11
const std::string message = "Hello!";
^

編譯器說無法初始化non-static data member,還說該功能只能在c++11(以上?)使用

non-static不行 那麼 static呢?

將第五行改成這樣:

1
const static std::string message = "Hello!";

嘗試編譯:

1
2
3
4
5
6
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:38: error: invalid in-class initialization of static data member of non-integral type ‘const string {aka const std::__cxx11::basic_string<char>}’
const static std::string message = "Hello!";
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:38: error: call to non-constexpr function‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string (const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’

第二個錯誤訊息是語意錯誤(Semantic error)與C++標準有關。

第一個錯誤訊息則是說:”非intergal型別的class內靜態data member初始化是無效的”

看來std::string是非integral type所以雖然是static type仍然無法在class內初始化

甚麼是Integral type?

在Quora上的問題 What are integra types in C++?

從答案可以很清楚地了解到Integral types是C++型別系統中對於型別的分類,屬於這類的Type有:

type
布林變數 bool
字元
窄字元 char
寬字元 char16_t
char32_t
wchar_t
整數 int

(包含使用unsigned或short/long keyword衍生的型別)

從上面列出的type觀察可以發現這些type大多很簡單,能用整數表示,以幾個byte的形式存於記憶體。floating point,當作反例,則是需要特別的encoding(如:IEEE-754)才能表示所存數值的type。

再怎麼查,得到的答案都是一個list告訴你那些type是Integral type。可是為需要區分是否為Integral type呢?WHY?

我猜可能跟ISA或compiler實作有關吧?

* ISA => Instruction Set Architecture

常數被指定後就不能被變更,編譯器可以很安心的把它當作macro般在程式碼中展開、inline。在編譯階段任何參照到常數的地方直接抽換成該常數的值。如此一來,常數在runtime可以說是不存在的,因為他的值已經inline到text section而非可以被讀取的變數,也就代表常數沒有address。

char或bool是以整數的形態存於記憶體中,C++裡面可以對char或bool可以像對待數字一樣進行加減乘除。或許被歸類在Integral type的資料可以直接給ALU做運算並依照其type再進行解釋(例如:非0整數作為bool為true)。ISA提供了運算operation,因此編譯器可以很放心的直接把bytes交給機器處理。

floating point或其他user data type可能就無法直接交給ISA處理了,因此沒辦法直接inline,在runtime需要有一塊記憶體儲存其data member也需要address以供參照。

char 是integral type那麼char[]是嗎?

Nope!

1
2
3
/home/winxp/project/c++Partices/greeting/greeting.cpp:6:33: error: invalid in-class initialization of static data member of non-integral type ‘const char []’
const static char message[] = "Hello!";
^

恩…我想也是。array本身代表連續的記憶體空間要怎麼inline呢?

Any other way?

C++之父 stroustrup的網站 提供了原因及方法。

原因

引自stroustrup 的網站

A class is typically declared in a header file and a header file is typically included into many translation units. However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.

通常class在header檔中定義,而header檔通常被許多翻譯單元include。為了避免複雜的linker發則C++要求每個物件都要有單一獨特的定義。若C++允許class內定義一個需要被儲存在記憶體的實體,那會打破此規則。

如果一個class的data member是另一個物件那麼可以說該class的定義相依於另一個物件,因為prototype definition主要給compiler決定要分配多少記憶體空間。因此要計算出此class所需的空間還得計算出其data member的空間。如果該data member也相依於其他物件,甚至是目前正在定義的物件…linker勢必要時做複雜的規則避免loop。

解決方法

要如何定義非intergal的data member?

解決方法即是不要使用in-class initialization。把初始化expression移到constructor或是class definition之外。

非static常數(將初始化移置constructor)

在constructor初始化常數。
跟java類似與許final/const留到constructor時才初始化,然而要在C++的constructor初始化常數必須透過initializer-list的方式。

1
2
3
4
5
6
7
8
9
10
11
class Greeting {
public:
//const static char message[] = "Hello!";
const std::string message;
Greeting() : message("Hello From constructor") {}
void greet() {
std::cout << message << std::endl;
}
};

static常數(使用out-of-class definition)

1
2
3
4
5
6
7
8
9
10
11
12
13
class Greeting {
public:
//const static char message[] = "Hello!";
static const std::string message;
//Greeting() : message("Hello From constructor") {}
void greet() {
std::cout << message << std::endl;
}
};
const std::string Greeting::message = "Hello from outside";