小編這次要給大家分享的是總結(jié)C++98/11/17表達式類別,文章內(nèi)容豐富,感興趣的小伙伴可以來了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。
創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計、漯河網(wǎng)絡(luò)推廣、小程序設(shè)計、漯河網(wǎng)絡(luò)營銷、漯河企業(yè)策劃、漯河品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供漯河建站搭建服務(wù),24小時服務(wù)熱線:18982081108,官方網(wǎng)址:jinyejixie.com
目標(biāo)
以下代碼能否編譯通過,能否按照期望運行?
#include <utility> #include <type_traits> namespace cpp98 { struct A { }; A func() { return A(); } int main() { int i = 1; i = 2; // 3 = 4; const int j = 5; // j = 6; i = j; func() = A(); return 0; } } namespace cpp11 { #define is_lvalue(x) std::is_lvalue_reference<decltype((x))>::value #define is_prvalue(x) !std::is_reference<decltype((x))>::value #define is_xvalue(x) std::is_rvalue_reference<decltype((x))>::value #define is_glvalue(x) (is_lvalue(x) || is_xvalue(x)) #define is_rvalue(x) (is_xvalue(x) || is_prvalue(x)) void func(); int non_reference(); int&& rvalue_reference(); std::pair<int, int> make(); struct Test { int field; void member_function() { static_assert(is_lvalue(field), ""); static_assert(is_prvalue(this), ""); } enum Enum { ENUMERATOR, }; }; int main() { int i; int&& j = std::move(i); Test test; static_assert(is_lvalue(i), ""); static_assert(is_lvalue(j), ""); static_assert(std::is_rvalue_reference<decltype(j)>::value, ""); static_assert(is_lvalue(func), ""); static_assert(is_lvalue(test.field), ""); static_assert(is_lvalue("hello"), ""); static_assert(is_prvalue(2), ""); static_assert(is_prvalue(non_reference()), ""); static_assert(is_prvalue(Test{3}), ""); static_assert(is_prvalue(test.ENUMERATOR), ""); static_assert(is_xvalue(rvalue_reference()), ""); static_assert(is_xvalue(make().first), ""); return 0; } } namespace reference { int&& rvalue_reference() { int local = 1; return std::move(local); } const int& const_lvalue_reference(const int& arg) { return arg; } int main() { auto&& i = rvalue_reference(); // dangling reference auto&& j = const_lvalue_reference(2); // dangling reference int k = 3; auto&& l = const_lvalue_reference(k); return 0; } } namespace auto_decl { int non_reference() { return 1; } int& lvalue_reference() { static int i; return i; } const int& const_lvalue_reference() { return lvalue_reference(); } int&& rvalue_reference() { static int i; return std::move(i); } int main() { auto [s1, s2] = std::pair(2, 3); auto&& t1 = s1; static_assert(!std::is_reference<decltype(s1)>::value); static_assert(std::is_lvalue_reference<decltype(t1)>::value); int i1 = 4; auto i2 = i1; decltype(auto) i3 = i1; decltype(auto) i4{i1}; decltype(auto) i5 = (i1); static_assert(!std::is_reference<decltype(i2)>::value, ""); static_assert(!std::is_reference<decltype(i3)>::value, ""); static_assert(!std::is_reference<decltype(i4)>::value, ""); static_assert(std::is_lvalue_reference<decltype(i5)>::value, ""); auto n1 = non_reference(); decltype(auto) n2 = non_reference(); auto&& n3 = non_reference(); static_assert(!std::is_reference<decltype(n1)>::value, ""); static_assert(!std::is_reference<decltype(n2)>::value, ""); static_assert(std::is_rvalue_reference<decltype(n3)>::value, ""); auto l1 = lvalue_reference(); decltype(auto) l2 = lvalue_reference(); auto&& l3 = lvalue_reference(); static_assert(!std::is_reference<decltype(l1)>::value, ""); static_assert(std::is_lvalue_reference<decltype(l2)>::value, ""); static_assert(std::is_lvalue_reference<decltype(l3)>::value, ""); auto c1 = const_lvalue_reference(); decltype(auto) c2 = const_lvalue_reference(); auto&& c3 = const_lvalue_reference(); static_assert(!std::is_reference<decltype(c1)>::value, ""); static_assert(std::is_lvalue_reference<decltype(c2)>::value, ""); static_assert(std::is_lvalue_reference<decltype(c3)>::value, ""); auto r1 = rvalue_reference(); decltype(auto) r2 = rvalue_reference(); auto&& r3 = rvalue_reference(); static_assert(!std::is_reference<decltype(r1)>::value, ""); static_assert(std::is_rvalue_reference<decltype(r2)>::value, ""); static_assert(std::is_rvalue_reference<decltype(r3)>::value, ""); return 0; } } namespace cpp17 { class NonMoveable { public: int i = 1; NonMoveable(int i) : i(i) { } NonMoveable(NonMoveable&&) = delete; }; NonMoveable make(int i) { return NonMoveable{i}; } void take(NonMoveable nm) { return static_cast<void>(nm); } int main() { auto nm = make(2); auto nm2 = NonMoveable{make(3)}; // take(nm); take(make(4)); take(NonMoveable{make(5)}); return 0; } } int main() { cpp98::main(); cpp11::main(); reference::main(); auto_decl::main(); cpp17::main(); }
C++98表達式類別
每個C++表達式都有一個類型:42的類型為int,int i;則(i)的類型為int&。這些類型落入若干類別中。在C++98/03中,每個表達式都是左值或右值。
左值(lvalue)是指向真實儲存在內(nèi)存或寄存器中的值的表達式?!發(fā)”指的是“l(fā)eft-hand side”,因為在C中只有l(wèi)value才能寫在賦值運算符的左邊。相對地,右值(rvalue,“r”指的是“right-hand side”)只能出現(xiàn)在賦值運算符的右邊。
有一些例外,如const int i;,i雖然是左值但不能出現(xiàn)在賦值運算符的左邊。到了C++,類類型的rvalue卻可以出現(xiàn)在賦值運算符的左邊,事實上這里的賦值是對賦值運算符函數(shù)的調(diào)用,與基本類型的賦值是不同的。
lvalue可以理解為可取地址的值,變量、對指針解引用、對返回類型為引用類型的函數(shù)的調(diào)用等,都是lvalue。臨時對象都是rvalue,包括字面量和返回類型為非引用類型的函數(shù)調(diào)用等。字符串字面量是個例外,它屬于不可修改的左值。
賦值運算符左邊需要一個lvalue,右邊需要一個rvalue,如果給它一個lvalue,該lvalue會被隱式轉(zhuǎn)換成rvalue。這個過程是理所當(dāng)然的。
動機
C++11引入了右值引用和移動語義。函數(shù)返回的右值引用,顧名思義,應(yīng)該表現(xiàn)得和右值一樣,但是這會破壞很多既有的規(guī)則:
這給傳統(tǒng)的lvalue/rvalue二分法帶來了挑戰(zhàn),C++委員會面臨選擇:
上述問題只是冰山一角;歷史選擇了第三種方案。
C++11表達式類別
C++11提出了表達式類別(value category)的概念。雖然名叫“value category”,但類別劃分的是表達式而不是值,所以我從標(biāo)題開始就把它譯為“表達式類別”。C++標(biāo)準(zhǔn)定義表達式為:
An expression is a sequence of operators and operands that specifies a computation. An expression can result in a value and can cause side effects.
每個表達式都是三種類別之一:左值(lvalue)、消亡值(xvalue)和純右值(prvalue),稱為主類別。還有兩種混合類別:lvalue和xvalue統(tǒng)稱范左值(glvalue),xvalue和prvalue統(tǒng)稱右值(rvalue)。
#define is_glvalue(x) (is_lvalue(x) || is_xvalue(x)) #define is_rvalue(x) (is_xvalue(x) || is_prvalue(x))
C++11對這些類別的定義如下:
這種定義不是很清晰。具體來講,lvalue包括:(點擊展開)
lvalue的性質(zhì):
prvalue包括:
prvalue的性質(zhì):
xvalue包括:
xvalue的性質(zhì);
glvalue的性質(zhì):
rvalue的性質(zhì):
還有一些特殊的分類:
終于把5個類別介紹完了。表達式可以分為lvalue、xvalue和prvalue三類,lvalue和prvalue與C++98中的lvalue和rvalue類似,而xvalue則完全是為右值引用而生,兼有g(shù)lvalue與rvalue的性質(zhì)。除了這種三分類法外,表達式還可以分為lvalue和rvalue兩類,它們之間的主要差別在于是否可以取地址;還可以分為glvalue和prvalue兩類,它們之間的主要差別在于是否存在實體,glvalue有實體,因而可以修改原對象,xvalue常被壓榨剩余價值。
引用綁定
我們稍微岔開一會,來看兩個與表達式分類相關(guān)的特性。
引用綁定有以下類型:
左值引用綁定lvalue天經(jīng)地義,沒什么需要關(guān)照的。但rvalue都是臨時對象,綁定給引用就意味著要繼續(xù)用它,它的生命周期會受到影響。通常,rvalue的生命周期會延長到綁定引用的聲明周期,但有以下例外:
簡而言之,臨時變量的生命周期只能延長一次。
#include <utility> int&& rvalue_reference() { int local = 1; return std::move(local); } const int& const_lvalue_reference(const int& arg) { return arg; } int main() { auto&& i = rvalue_reference(); // dangling reference auto&& j = const_lvalue_reference(2); // dangling reference int k = 3; auto&& l = const_lvalue_reference(k); }
rvalue_reference返回一個指向局部變量的引用,因此i是空懸引用;2綁定到const_lvalue_reference的參數(shù)arg上,函數(shù)返回后延長的生命周期達到終點,因此j也是懸空引用;k在傳參的過程中根本沒有臨時對象創(chuàng)建出來,所以l不是空懸引用,它是指向k的const左值引用。
auto與decltype
從C++11開始,auto關(guān)鍵字用于自動推導(dǎo)類型,用的是模板參數(shù)推導(dǎo)的規(guī)則:如果是拷貝列表初始化,則對應(yīng)模板參數(shù)為std::initializer_list<T>,否則把auto替換為T。至于詳細的模板參數(shù)推導(dǎo)規(guī)則,要介紹的話未免喧賓奪主了。
還好,這不是我們的重點。在引出重點之前,我們還得先看decltype。
decltype用于聲明一個類型("declare type"),有兩種語法:
第一種,decltype的參數(shù)是沒有括號包裹的標(biāo)識符或類成員,則decltype產(chǎn)生該實體的類型;如果是結(jié)構(gòu)化綁定,則產(chǎn)生被引類型。
第二種,decltype的參數(shù)是不能匹配第一種的任何表達式,其類型為T,則根據(jù)其表達式類別討論:
因此,decltype(x)和decltype((x))產(chǎn)生的類型通常是不同的。
對于不帶引用修飾的auto,初始化器的表達式類別會被抹去,為此C++14引入了新語法decltype(auto),產(chǎn)生的類型為decltype(expr),其中expr為初始化器。對于局部變量,等號右邊加上一對圓括號,可以保留表達式類別。
#include <utility> #include <type_traits> int non_reference() { return 1; } int& lvalue_reference() { static int i; return i; } const int& const_lvalue_reference() { return lvalue_reference(); } int&& rvalue_reference() { static int i; return std::move(i); } int main() { auto [s1, s2] = std::pair(2, 3); auto&& t1 = s1; static_assert(!std::is_reference<decltype(s1)>::value); static_assert(std::is_lvalue_reference<decltype(t1)>::value); int i1 = 4; auto i2 = i1; decltype(auto) i3 = i1; decltype(auto) i4{i1}; decltype(auto) i5 = (i1); static_assert(!std::is_reference<decltype(i2)>::value); static_assert(!std::is_reference<decltype(i3)>::value); static_assert(!std::is_reference<decltype(i4)>::value); static_assert(std::is_lvalue_reference<decltype(i5)>::value); auto n1 = non_reference(); decltype(auto) n2 = non_reference(); auto&& n3 = non_reference(); static_assert(!std::is_reference<decltype(n1)>::value, ""); static_assert(!std::is_reference<decltype(n2)>::value, ""); static_assert(std::is_rvalue_reference<decltype(n3)>::value, ""); auto l1 = lvalue_reference(); decltype(auto) l2 = lvalue_reference(); auto&& l3 = lvalue_reference(); static_assert(!std::is_reference<decltype(l1)>::value, ""); static_assert(std::is_lvalue_reference<decltype(l2)>::value, ""); static_assert(std::is_lvalue_reference<decltype(l3)>::value, ""); auto c1 = const_lvalue_reference(); decltype(auto) c2 = const_lvalue_reference(); auto&& c3 = const_lvalue_reference(); static_assert(!std::is_reference<decltype(c1)>::value, ""); static_assert(std::is_lvalue_reference<decltype(c2)>::value, ""); static_assert(std::is_lvalue_reference<decltype(c3)>::value, ""); auto r1 = rvalue_reference(); decltype(auto) r2 = rvalue_reference(); auto&& r3 = rvalue_reference(); static_assert(!std::is_reference<decltype(r1)>::value, ""); static_assert(std::is_rvalue_reference<decltype(r2)>::value, ""); static_assert(std::is_rvalue_reference<decltype(r3)>::value, ""); }
用auto定義的變量都是int類型,無論函數(shù)的返回類型的引用和const修飾;用decltype(auto)定義的變量的類型與函數(shù)返回類型相同;auto&&是轉(zhuǎn)發(fā)引用,n3類型為int&&,其余與decltype(auto)相同。
C++17表達式類別
眾所周知,編譯器常會執(zhí)行NRVO(named return value optimization),減少一次對函數(shù)返回值的移動或拷貝。不過,這屬于C++標(biāo)準(zhǔn)說編譯器可以做的行為,卻沒有保證編譯器會這么做,因此客戶不能對此作出假設(shè),從而需要提供一個拷貝或移動構(gòu)造函數(shù),盡管它們可能不會被調(diào)用。然而,并不是所有情況下都能提供移動構(gòu)造函數(shù),即使能移動構(gòu)造函數(shù)也未必只是一個指針的交換??傊覀兠髦苿訕?gòu)造函數(shù)不會被調(diào)用卻還要硬著頭皮提供一個,這樣做非常形式主義。
所以,C++17規(guī)定了拷貝省略,確保在以下情況下,即使拷貝或移動構(gòu)造函數(shù)有可觀察的效果,它們也不會被調(diào)用,原本要拷貝或移動的對象直接在目標(biāo)位置構(gòu)造:
值得一提的是,這類行為在C++17中不能算是一種優(yōu)化,因為不存在用來拷貝或移動的臨時對象。事實上,C++17重新定義了表達式類別:
這個定義在功能上與C++11中的相同,但是更清晰地指出了glvalue和prvalue的區(qū)別——glvalue產(chǎn)生地址,prvalue執(zhí)行初始化。
prvalue初始化的對象由上下文決定:在拷貝省略的情形下,prvalue不曾有關(guān)聯(lián)的對象;其他情形下,prvalue將產(chǎn)生一個臨時對象,這個過程稱為臨時實體化(temporary materialization)。
臨時實體化把一個完全類型的prvalue轉(zhuǎn)換成xvalue,在以下情形中發(fā)生:
或者可以理解為,所有非拷貝省略的場合中的prvalue都會被臨時實體化。
class NonMoveable { public: int i = 1; NonMoveable(int i) : i(i) { } NonMoveable(NonMoveable&&) = delete; }; NonMoveable make(int i) { return NonMoveable{i}; } void take(NonMoveable nm) { return static_cast<void>(nm); } int main() { auto nm = make(2); auto nm2 = NonMoveable{make(3)}; // take(nm); take(make(4)); take(NonMoveable{make(5)}); }
NonMoveable的移動構(gòu)造函數(shù)被聲明為delete,于是拷貝構(gòu)造函數(shù)也被隱式delete。在auto nm = make(2);中,NonMoveable{i}為prvalue,根據(jù)拷貝省略的第一條規(guī)則,它直接構(gòu)造為返回值;返回值是NonMoveable的prvalue,與nm類型相同,根據(jù)第二條規(guī)則,這個prvalue直接在nm的位置上構(gòu)造;兩部分結(jié)合,該聲明式相當(dāng)于NonMoveable nm{2};。
在MSVC中,這段代碼不能通過編譯,這是編譯器未能嚴格遵守C++標(biāo)準(zhǔn)的緣故。然而,如果在NonMoveable的移動構(gòu)造函數(shù)中添加輸出語句,程序運行起來也沒有任何輸出,即使在Debug模式下、即使用C++11標(biāo)準(zhǔn)編譯都如此。這也側(cè)面反映出拷貝省略的意義。
總結(jié)
C++11規(guī)定每個表達式都屬于lvalue、xvalue和prvalue三個類別之一,表達式另可分為lvalue和rvalue,或glvalue和prvalue。返回右值引用的函數(shù)調(diào)用是xvalue,右值引用類型的變量是lvalue。
const左值引用和右值引用可以綁定臨時對象,但是臨時對象的聲明周期只能延長一次,返回一個指向局部變量的右值引用也會導(dǎo)致空懸引用。
標(biāo)識符加上一對圓括號成為表達式,decltype用于表達式可以根據(jù)其類別產(chǎn)生相應(yīng)的類型,用decltype(auto)聲明變量可以保留表達式類別。
C++17中prvalue是否有關(guān)聯(lián)對象由上下文決定,拷貝省略規(guī)定了特定情況下對象不經(jīng)拷貝或移動直接構(gòu)造,NRVO成為強制性標(biāo)準(zhǔn),使不能被移動的對象在語義上可以值傳遞。
看完這篇關(guān)于總結(jié)C++98/11/17表達式類別的文章,如果覺得文章內(nèi)容寫得不錯的話,可以把它分享出去給更多人看到。
分享名稱:總結(jié)C++98/11/17表達式類別
當(dāng)前地址:http://jinyejixie.com/article24/jdogce.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導(dǎo)航、服務(wù)器托管、Google、標(biāo)簽優(yōu)化、網(wǎng)頁設(shè)計公司、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)