C++(二):如何確定表示式的值型別?

C++(二):如何確定表示式的值型別?

C++本來只有左值和右值,但是為了能充分利用右值,減少記憶體的分配,從而引入了將亡值。左值可以透過std::move()轉換成將亡值,右值也可以透過std::move()或者隱式型別轉換變為將亡值。將亡值僅僅是個標記,表示該表示式所持有的資源,可以被偷取。

如何打印表達式的型別資訊?

templatestd::string type_to_string() {#if defined(_MSC_VER) std::string type_name{__FUNCSIG__}; // class std::basic_string,class std::allocator > __cdecl type_to_string(void) auto start_pos = type_name。find_first_of(‘<’, std::string(typeid(std::string)。name())。size()) + 1; auto end_pos = type_name。find_last_of(‘>’); return type_name。substr(start_pos, (end_pos - start_pos));#elif defined(__clang__) std::string type_name{ __PRETTY_FUNCTION__ }; auto start_pos = type_name。find_first_of(‘=’) + 2; auto end_pos = type_name。find_first_of(‘]’, start_pos); return type_name。substr(start_pos, (end_pos-start_pos));#elif defined(__GNUC__) std::string type_name{ __PRETTY_FUNCTION__ }; // std::__cxx11::string type_to_string() [with T = int&; std::__cxx11::string = std::__cxx11::basic_string] auto start_pos = type_name。find_first_of(‘=’) + 2; auto end_pos = type_name。find_first_of(‘;’, start_pos); return type_name。substr(start_pos, (end_pos-start_pos));#endif}

在C++中如何得到一個表示式的值型別?

int a = 10; // decltype可以推匯出一個表示式的型別 std::cout << “The type of a : ” << type_to_string() << std::endl; // 如果多加一個括號可以得到值型別 std::cout << “The value type of a : ” << type_to_string() << std::endl; std::cout << “The value type of std::move(a) : ” << type_to_string() << std::endl; std::cout << “The type of 10 : ” << type_to_string() << std::endl; std::cout << “The value type of 10 : ” << type_to_string() << std::endl;

輸出:

// a的型別The type of a : int// a的值型別The value type of a : int&// std::move(a)的值型別The value type of a : int&&// 10的型別The type of 10 : int// 10的值型別The value type of 10 : int

從上面這幾個例子可以看出,

decltype((*))

推匯出的值型別,分為三類:

&&作為限定符的將亡值(xvalue)

&作為限定符的左值(lvalue)

無限定符的純右值(prvalue)

從而,可以透過std::is_lvalue_reference_v和std::is_rvalue_reference_v這兩個模板類來判斷左值和將亡值,不屬於它們中任意一個的就是純右值。 程式碼如下:

templateconcept is_lvalue = std::is_lvalue_reference_v;templateconcept is_xvalue = std::is_rvalue_reference_v;templateconcept is_prvalue = !(is_lvalue || is_xvalue);templateconcept is_rvalue = is_prvalue || is_xvalue;templateconcept is_glvalue = is_lvalue || is_xvalue

使用時傳入的模板引數是decltype((a)),這樣的形式。

注意

:上述concept只能用於判定值型別,無法實際在模板中使用,因為模板中傳入的是型別引數。

再定義一個宏:

#define value_type(value) \ do \ { \ std::cout << #value “ is lvalue : ” \ << is_lvalue << std::endl; \ std::cout << #value “ is xvalue : ” \ << is_xvalue << std::endl; \ std::cout << #value “ is prvalue : ” \ << is_prvalue << std::endl; \ std::cout << #value “ is rvalue : ” \ << is_rvalue << std::endl; \ std::cout << #value “ is glvalue : ” \ << is_glvalue << std::endl; \ } while (0)\

使用

std::cout << std::boolalpha; value_type(10); int a = 10; std::cout << “===========================” << std::endl; value_type(a); std::cout << “===========================” << std::endl; value_type(std::move(a));

輸出:

10 is lvalue : false10 is xvalue : false10 is prvalue : true10 is rvalue : true10 is glvalue : false===========================a is lvalue : truea is xvalue : falsea is prvalue : falsea is rvalue : falsea is glvalue : true===========================std::move(a) is lvalue : falsestd::move(a) is xvalue : truestd::move(a) is prvalue : falsestd::move(a) is rvalue : truestd::move(a) is glvalue : true

10是個純右值,同時也屬於右值;a是左值,同時也屬於glvalue;std::move(a)是一個將亡值,所以他也屬於rvalue和glvalue。

為什麼上面的例子要使用std::move(a),而不直接使用一個右值引用型別的變數呢?下面來看下,一個右值引用型別的變數的型別和值型別。

int&& b = 20; std::cout << “The type of b : ” << type_to_string() << std::endl; value_type(b);

輸出:

The type of b : int&&b is lvalue : trueb is xvalue : falseb is prvalue : falseb is rvalue : falseb is glvalue : true

從上面的輸出可以看出,雖然b的型別為右值引用,但他卻是個左值。int&& b = 20 這一表達式,實際上是先為20在棧上分配一塊4位元組的記憶體,再將20存到這片記憶體中,再將記憶體地址賦值給b。

mov eax, 20mov DWORD PTR [rbp-12], eaxlea rax, [rbp-12]mov QWORD PTR [rbp-8], raxrbp 棧底地址rsp 棧頂地址dword 雙字 就是四個位元組ptr pointer縮寫 即指標[]裡的資料是一個地址值,這個地址指向一個雙字型資料比如mov DWORD PTR [rbp-12], eax 把記憶體地址rbp-12中的雙字型(32位)資料賦給eax lea load effective address 比如:lea rax, [rbp-12] 將rbp-12這個地址賦值給rax

表示式值型別的具體分類

lvalue

struct Person{ int age; static int weight;};int& f() { int* a = new int{10}; return *a;}void g() {}int main(){ std::cout << std::boolalpha; // 1。 普通變數 int a = 10; std::cout << “a is lvalue : ” << is_lvalue << std::endl; // a is lvalue : true // 2。 函式 std::cout << “main() is lvalue : ” << is_lvalue << std::endl; // main() is lvalue : true // 3。 成員變數 Person p; std::cout << “p。age is lvalue : ” << is_lvalue << std::endl; std::cout << “Person::weight is lvalue : ” << is_lvalue << std::endl; // p。age is lvalue : true // Person::weight is lvalue : true // 4。 字元型別的字面量 std::cout << R“(”hello world“ is lvalue : )” << is_lvalue << std::endl; std::cout << R“(The type of ”hello world“ : )” << type_to_string() << std::endl; // “hello world” is lvalue : true // The type of “hello world” : const char (&)[12] // 5。 左值引用型別的函式返回值 std::cout << “f()->int& is lvalue : ” << is_lvalue << std::endl; // f()->int& is lvalue : true // 6。 函式不管怎麼變都是左值 std::cout << R“(The type of g : )” << type_to_string() << std::endl; // The type of f : int() void(&g1)() = g; std::cout << R“(The type of g1 : )” << type_to_string() << std::endl; std::cout << R“(&g is lvalue : )” << is_lvalue << std::endl; // The type of g1 : void (&)() // &g is lvalue : true void(&&g2)() = g; std::cout << R“(The type of g2 : )” << type_to_string() << std::endl; std::cout << R“(&&g is lvalue : )” << is_lvalue << std::endl; // The type of g2 : void (&&)() // &&g is lvalue : true std::cout << R“(The type of std::move(g) : )” << type_to_string() << std::endl; std::cout << R“(std::move(g) is lvalue : )” << is_lvalue << std::endl; // The type of std::move(g) : void (&)() // std::move(g) is lvalue : true}

prvalue

std::cout << std::boolalpha; // 1。 除字串字面量以外的其它字面量 std::cout << “10 is prvalue : ” << is_prvalue << std::endl; std::cout << “true is prvalue : ” << is_prvalue << std::endl; std::cout << “nullptr is prvalue : ” << is_prvalue << std::endl; // 10 is prvalue : true // true is prvalue : true // nullptr is prvalue : true // 2。 函式返回值 std::cout << “main()->int is prvalue : ” << is_prvalue << std::endl; // main()->int is prvalue : true // 3。 臨時物件 std::cout << “Person{} is prvalue : ” << is_prvalue << std::endl; // 4。 取地址符 std::cout << “& is prvalue : ” << is_prvalue << std::endl; // 5。 lambda表示式 std::cout << “lambda is prvalue : ” << is_prvalue << std::endl;

xvalue

struct Person{ int age; static int weight;};int& f() { int* a = new int{10}; return *a;}void g() {}int&& h(){ return 10;}int main(){ std::cout << std::boolalpha; // 1。 std::move int a = 10; std::cout << “std::move(10) is xvalue : ” << is_xvalue << std::endl; std::cout << “std::move(a) is xvalue : ” << is_xvalue << std::endl; // std::move(10) is xvalue : true // std::move(a) is xvalue : true // 2。 static_cast std::cout << “static_cast(10) is xvalue : ” << is_xvalue(10)))> << std::endl; std::cout << “static_cast(a) is xvalue : ” << is_xvalue(a)))> << std::endl; // 3。 函式返回值為T&&型別 std::cout << “h()->int&& is xvalue : ” << is_xvalue << std::endl; // h()->int&& is xvalue : true // 4。 rvalue 物件的非靜態成員 Person p; std::cout << “Person{}。age is xvalue : ” << is_xvalue << std::endl; std::cout << “std::move(p)。age is xvalue : ” << is_xvalue << std::endl; // Person{}。age is xvalue : true // std::move(p)。age is xvalue : true}

std::move中的一個坑

struct Person{ int age; static int weight; std::string& name;};int main(){ std::cout << std::boolalpha; std::string name = “xiaoming”; Person p{10, name}; std::cout << “===============” << std::endl; value_type(std::move(p)。age); std::cout << “===============” << std::endl; value_type(std::move(p)。weight); std::cout << “===============” << std::endl; value_type(std::move(p)。name);}===============std::move(p)。age is lvalue : falsestd::move(p)。age is xvalue : truestd::move(p)。age is prvalue : falsestd::move(p)。age is rvalue : truestd::move(p)。age is glvalue : true===============std::move(p)。weight is lvalue : truestd::move(p)。weight is xvalue : falsestd::move(p)。weight is prvalue : falsestd::move(p)。weight is rvalue : falsestd::move(p)。weight is glvalue : true===============std::move(p)。name is lvalue : truestd::move(p)。name is xvalue : falsestd::move(p)。name is prvalue : falsestd::move(p)。name is rvalue : falsestd::move(p)。name is glvalue : true

從上述輸出,可以看出std::move(p)。age是個xvalue; std::move(p)。weight和std::move(p)。name都是lvalue。也就是說,weight和name都不是Person類的一部分,Person類對於這兩個成員變數沒有所有權。應改為std::move(p。weight)和std::move(p。name),如果希望改變它們的值型別的話。

std::cout << std::boolalpha; std::string name = “xiaoming”; Person p{10, name}; std::cout << “===============” << std::endl; value_type(std::move(p。age)); std::cout << “===============” << std::endl; value_type(std::move(p。weight)); std::cout << “===============” << std::endl; value_type(std::move(p。name));===============std::move(p。age) is lvalue : falsestd::move(p。age) is xvalue : truestd::move(p。age) is prvalue : falsestd::move(p。age) is rvalue : truestd::move(p。age) is glvalue : true===============std::move(p。weight) is lvalue : falsestd::move(p。weight) is xvalue : truestd::move(p。weight) is prvalue : falsestd::move(p。weight) is rvalue : truestd::move(p。weight) is glvalue : true===============std::move(p。name) is lvalue : falsestd::move(p。name) is xvalue : truestd::move(p。name) is prvalue : falsestd::move(p。name) is rvalue : truestd::move(p。name) is glvalue : true

這樣,三個成員變數都變成了xvalue。