整型字面值,例如42,就是常量表達式。所以,簡單的數(shù)學表達式,例如,23x2-4。可以使用其來初始化const整型變量,然后將const整型變量作為新表達的一部分:
const int i=23;
const int two_i=i*2;
const int four=4;
const int forty_two=two_i-four;
使用常量表達式創(chuàng)建變量也可用在其他常量表達式中,有些事只能用常量表達式去做:
int bounds=99;
int array[bounds]; // 錯誤,bounds不是一個常量表達式
const int bounds2=99;
int array2[bounds2]; // 正確,bounds2是一個常量表達式
template<unsigned size>
struct test
{};
test<bounds> ia; // 錯誤,bounds不是一個常量表達式
test<bounds2> ia2; // 正確,bounds2是一個常量表達式
class X
{
static const int the_answer=forty_two;
};
struct my_aggregate
{
int a;
int b;
};
static my_aggregate ma1={forty_two,123}; // 靜態(tài)初始化
int dummy=257;
static my_aggregate ma2={dummy,dummy}; // 動態(tài)初始化
這些都不是新添加的——你可以在1998版本的C++標準中找到對應上面實例的條款。不過,新標準中常量表達式進行了擴展,并添加了新的關鍵字——constexpr
。
constexpr
會對功能進行修改,當參數(shù)和函數(shù)返回類型符合要求,并且實現(xiàn)很簡單,那么這樣的函數(shù)就能夠被聲明為constexpr
,這樣函數(shù)可以當做常數(shù)表達式來使用:
constexpr int square(int x)
{
return x*x;
}
int array[square(5)];
在這個例子中,array有25個元素,因為square函數(shù)的聲明為constexpr
。當然,這種方式可以當做常數(shù)表達式來使用,不意味著什么情況下都是能夠自動轉換為常數(shù)表達式:
int dummy=4;
int array[square(dummy)]; // 錯誤,dummy不是常數(shù)表達式
dummy不是常數(shù)表達式,所以square(dummy)也不是——就是一個普通函數(shù)調用——所以其不能用來指定array的長度。
目前為止的例子都是以內置int型展開的。不過,在新C++標準庫中,對于滿足字面類型要求的任何類型,都可以用常量表達式來表示。
要想劃分到字面類型中,需要滿足一下幾點:
一般的拷貝構造函數(shù)。
一般的析構函數(shù)。
所有成員變量都是非靜態(tài)的,且基類需要是一般類型。
后面會了解一下constexpr構造函數(shù)。
現(xiàn)在,先將注意力集中在默認構造函數(shù)上,就像下面清單中的CX類一樣。
清單A.3(一般)默認構造函數(shù)的類
class CX
{
private:
int a;
int b;
public:
CX() = default; // 1
CX(int a_, int b_): // 2
a(a_),b(b_)
{}
int get_a() const
{
return a;
}
int get_b() const
{
return b;
}
int foo() const
{
return a+b;
}
};
注意,這里顯式的聲明了默認構造函數(shù)①(見A.3節(jié)),為了保存用戶定義的構造函數(shù)②。因此,這種類型符合字面類型的要求,可以將其用在常量表達式中。
可以提供一個constexpr函數(shù)來創(chuàng)建一個實例,例如:
constexpr CX create_cx()
{
return CX();
}
也可以創(chuàng)建一個簡單的constexpr函數(shù)來拷貝參數(shù):
constexpr CX clone(CX val)
{
return val;
}
不過,constexpr函數(shù)只有其他constexpr函數(shù)可以進行調用。CX類中聲明成員函數(shù)和構造函數(shù)為constexpr:
class CX
{
private:
int a;
int b;
public:
CX() = default;
constexpr CX(int a_, int b_):
a(a_),b(b_)
{}
constexpr int get_a() const // 1
{
return a;
}
constexpr int get_b() // 2
{
return b;
}
constexpr int foo()
{
return a+b;
}
};
注意,const對于get_a()①來說就是多余的,因為在使用constexpr時就為const了,所以const描述符在這里會被忽略。
這就允許更多復雜的constexpr函數(shù)存在:
constexpr CX make_cx(int a)
{
return CX(a,1);
}
constexpr CX half_double(CX old)
{
return CX(old.get_a()/2,old.get_b()*2);
}
constexpr int foo_squared(CX val)
{
return square(val.foo());
}
int array[foo_squared(half_double(make_cx(10)))]; // 49個元素
函數(shù)都很有趣,如果想要計算數(shù)組的長度或一個整型常量,就需要使用這種方式。最大的好處是常量表達式和constexpr函數(shù)會設計到用戶定義類型的對象,可以使用這些函數(shù)對這些對象進行初始化。因為常量表達式的初始化過程是靜態(tài)初始化,所以就能避免條件競爭和初始化順序的問題:
CX si=half_double(CX(42,19)); // 靜態(tài)初始化
當構造函數(shù)被聲明為constexpr,且構造函數(shù)參數(shù)是常量表達式時,那么初始化過程就是常數(shù)初始化(可能作為靜態(tài)初始化的一部分)。隨著并發(fā)的發(fā)展,C++11標準中有一個重要的改變:允許用戶定義構造函數(shù)進行靜態(tài)初始化,就可以在初始化的時候避免條件競爭,因為靜態(tài)過程能保證初始化過程在代碼運行前進行。
特別是關于std::mutex
(見3.2.1節(jié))或std::atomic<>
(見5.2.6節(jié)),當想要使用一個全局實例來同步其他變量的訪問時,同步訪問就能避免條件競爭的發(fā)生。構造函數(shù)中,互斥量不可能產(chǎn)生條件競爭,因此對于std::mutex
的默認構造函數(shù)應該被聲明為constexpr,為了保證互斥量初始化過程是一個靜態(tài)初始化過程的一部分。
目前,已經(jīng)了解了constexpr在函數(shù)上的應用。constexpr也可以用在對象上,主要是用來做判斷的;驗證對象是否是使用常量表達式,constexpr構造函數(shù)或組合常量表達式進行初始化。
且這個對象需要聲明為const:
constexpr int i=45; // ok
constexpr std::string s(“hello”); // 錯誤,std::string不是字面類型
int foo();
constexpr int j=foo(); // 錯誤,foo()沒有聲明為constexpr
將一個函數(shù)聲明為constexpr,也是有幾點要求的;當不滿足這些要求,constexpr聲明將會報編譯錯誤。
所有參數(shù)都必須是字面類型。
返回類型必須是字面類型。
函數(shù)體內必須有一個return。
return的表達式需要滿足常量表達式的要求。
看起來很簡單,要在內聯(lián)函數(shù)中使用到常量表達式,返回的還是個常量表達式,還不能對任何東西進行改動。constexpr函數(shù)就是無害的純潔的函數(shù)。
constexpr類成員函數(shù),需要追加幾點要求:
constexpr成員函數(shù)不能是虛函數(shù)。
constexpr構造函數(shù)的規(guī)則也有些不同:
構造函數(shù)體必須為空。
每一個基類必須可初始化。
每個非靜態(tài)數(shù)據(jù)成員都需要初始化。
初始化列表的任何表達式,必須是常量表達式。
構造函數(shù)可選擇要進行初始化的數(shù)據(jù)成員,并且基類必須有constexpr構造函數(shù)。
這些條件同樣適用于成員函數(shù),除非函數(shù)沒有返回值,也就沒有return語句。
另外,構造函數(shù)對初始化列表中的所有基類和數(shù)據(jù)成員進行初始化。一般的拷貝構造函數(shù)會隱式的聲明為constexpr。
將constexpr應用于函數(shù)模板,或一個類模板的成員函數(shù);根據(jù)參數(shù),如果模板的返回類型不是字面類,編譯器會忽略其常量表達式的聲明。當模板參數(shù)類型合適,且為一般inline函數(shù),就可以將類型寫成constexpr類型的函數(shù)模板。
template<typename T>
constexpr T sum(T a,T b)
{
return a+b;
}
constexpr int i=sum(3,42); // ok,sum<int>是constexpr
std::string s=
sum(std::string("hello"),
std::string(" world")); // 也行,不過sum<std::string>就不是constexpr了
函數(shù)需要滿足所有constexpr函數(shù)所需的條件。不能用多個constexpr來聲明一個函數(shù),因為其是一個模板;這樣也會帶來一些編譯錯誤。