C ++值类别的快速参考:第1部分

图片

此快速参考的目标是在一个地方收集并组织有关C ++中的值类别,赋值,参数传递和从函数返回的信息。 我试图使此快速参考变得方便,以便快速比较和选择可能的解决方案之一,这就是为什么我在这里制作了几张表。


有关该主题的介绍,请使用以下链接:


C ++右值引用和初学者的移动语义
重新定义右值
C ++为那些不知道或不在乎什么右值的人而行动
斯科特·迈耶斯(Scott Meyers)。 有效的现代C ++。 2015年
了解移动语义和完美转发:第1部分
了解移动语义和完美转发:第2部分
了解移动语义和完美转发:第3部分
是否需要移动和复制作业


值类别


图片


从C ++ 11开始,任何表达式只能属于以下三个值类别之一:lvalue,xvalue或prvalue:


  • xvalue(到期值)-表达式,它标识可以移动的非临时对象
  • 左值(左值)-表达式,它标识函数的非临时对象,该对象不能移动
  • prvalue(纯右值)-表达式,用于初始化对象

图片


这三个类别可以分为三个重叠的组:


  • glvalue(广义左值)将左值和左值分组。 glvalue在内存中具有地址(具有标识),因此通常可以分配一个值(如果不是const)
  • 右值将prvalue和xvalue分组。 rvalue可以移动(xvalue)或根本不属于现有对象(prvalue)。 可以将右值传递给移动构造函数,移动赋值运算符或函数

因此,xvalue在内存中有一个地址,可以移动。


图片


从上表中可以看到,左值,左值引用和右值引用类别的命名变量都具有相同的表达式类别:左值(以红色突出显示)。 例如,这意味着,当右值引用传递给函数时,将选择左值引用重载: T&& x=T(); f(x); T&& x=T(); f(x);


友情链接:
C ++左值rvalue右值glvalue prvalue
C ++ 17中的值类别
值类别


本文中的缩写


构造函数,运算符和析构函数的缩写:


  • DC-默认构造函数
  • PC-参数化构造函数
  • CC-复制构造函数
  • Ca-复制分配(可以重复使用分配的存储,例如用于std :: string)
  • Mc-Move构造函数
  • 马-搬家作业
  • Va-转化分配运算符
  • D-析构函数

值类别的缩写:


  • LV-Lvalue: T LV;
  • LR-左值参考: T& LR = LV;
  • XV-Xvalue: move(LV)
  • PRV-纯右值。 函数调用的文字或结果,返回类型不是引用: T f() { return T(); } T f() { return T(); }
  • FR-转发参考: auto&& FR = LV;
  • CLV-常量LV。 CLR-常量LR。 CXV-常量XV。
  • CPRV-常量PRV。 CPRV仅适用于类和数组。 像cont int这样的非类非数组CPRV将像int一样隐式转换为PRV,例如在函数const int f()返回值中)
  • RV是XV或PRV
  • CRV是CXV或CPRV

其他缩写:


  • PF-完美转发(使用模板化转发引用将参数传递给功能)

分配值类别


值类别的分配可以视为以下所有部分的基础。 在不进行显式类别转换的情况下将一个类别显式分配给另一类别时,将发生隐式转换: FROM_TYPE x; TO_TYPE y; y = x; FROM_TYPE x; TO_TYPE y; y = x; 在下表中,您可以看到将From类型的表达式分配给To类型的变量时会发生什么:


  • 减号表示不可能进行这种分配
  • 加号表示可以进行这种分配,并且不需要调用复制/移动运算符或构造函数。
  • 在其他情况下,会有文本,这意味着在这种分配过程中将调用哪些复制/移动操作符和构造函数。

从返回的表达式类别到函数返回值类型的隐式转换不能用红色字体标记-有关详细信息,请参见下面的“从函数返回”部分。
常量类型的行以蓝色突出显示。


图片


脚注:


1-const auto &&引用不是转发引用,而是const rvalue引用,因此它们的作用类似于const T &&
2-创建的用于保存引用初始化程序的临时对象将一直保留到其引用范围的结尾(请参阅下一节)。 无法将返回值转换为函数类型。
3-传递文字时,需要在右侧输入auto的类型说明符: auto&& = T{2};


注意事项:


  • 非常量左值和右值引用不能接受任何常量类型。 但是非const非引用类型可以接受const类型。
  • 右值引用(T &&)可以接受xvalue或prvalue(XV或PRV)。
  • 左值参考(T&)可以接受左值或左值参考(LV或LR)
  • 非引用类型通过转换,复制,移动或RVO接受任何类型
  • const左值引用(const T&,const auto&)和自动转发引用(auto &&)接受任何类型,而无需复制或移动
  • 常量和非常量右值引用(T &&,常量T &&)不能接受常量或非常量左值(LV或CLV)。
  • LR不能接受常量或非常量右值引用(XV或PRV或CXV或CPRV)。 CLR可以接受它们。
  • 具有const修饰符的类型不能分配给没有const修饰符的任何类型(auto除外,如果const在右侧显式,则可以变为const)。
  • 传递prvalue(传递返回值,文字或构造函数结果)与传递xvalue相同,但在分配给非引用类型时避免调用move构造函数
  • 传递const prvalue(传递const类型函数的返回值)与传递const xvalue相同,但在分配给非引用类型时避免调用副本构造函数
  • 自动引用可以成为const,因此它们可以接受const和非const变体。 而且,它们可以成为const引用,以使临时持久化直到引用范围结束。 但是,如果未在右侧明确指定,则auto *引用将无法成为const。 这就是为什么您不能将rvalue分配给auto&的原因
  • const引用可以接收一个临时值,并使它持久直到引​​用范围的结尾(非const引用不能)。 这仅在当前作用域(局部函数引用和函数参数)内起作用。 如果reference是类的成员,则临时对象将在构造函数或函数的结尾处被破坏,并且引用将继续指向被破坏的对象,这是未定义的行为。
  • Auto x = CLV; 将推断出auto为非const类型。

打印每个被调用的构造函数和运算符的示例和测试
 #include <iostream> #include <iomanip> #include <map> #include <vector> #include <string> using namespace std; template<class C, class T> auto contains(const C& v, const T& x) -> decltype(end(v), true) { return end(v) != std::find(begin(v), end(v), x); } template <class... Types> constexpr inline __attribute__((__always_inline__)) int UNUSED(Types&&...) { return 0; }; map<string, map<string, string>> res; vector<string> froms; vector<string> tos; string from; string to; void report(string st) { if (!from.empty() && !to.empty()) { res[from][to] += st; } cout << st << " "; } struct T { T() { report("Dc"); } T(int va) : a(va) { report("Pc"); } T(const T& other) : a(other.a) { report("Cc"); } T(T&& other) : a(std::exchange(other.a, 0)) { report("Mc"); } T& operator=(int va) { report("Va"); a = va; return *this; } T& operator=(const T& rhs) { report("Ca"); // check for self-assignment if(&rhs == this) return *this; a = rhs.a; return *this; } T& operator=(T&& rhs) { report("Ma"); // check for self-assignment if(&rhs == this) return *this; a = std::exchange(rhs.a, 0); return *this; } ~T() { report("D"); } int a = 1; }; T Fprv() { return T(); } const T Fcprv() { return T(); } void print_col(const string &st, int width) { cout << endl << left << setw(width) << st; } void test_assign(string lto, string lfrom) { from = lfrom; to = lto; res[from][to] = ""; if (!from.empty() && !to.empty()) { if (!contains(froms, from)) froms.push_back(from); if (!contains(tos, to)) tos.push_back(to); } print_col(lto + " = " + lfrom + ": ", 20); } #define TEST_ASSIGN(t, v) { \ test_assign(#t, #v); \ ts = v; \ cout << sa; \ UNUSED(s); \ cout << "-"; \ } void test_conversion() { T l; const T cl; T& lr = l; const T& clr = l; T&& rr = T(); const T&& crr = T(); auto &&fr = T(); TEST_ASSIGN(T, 8); TEST_ASSIGN(T, T()); TEST_ASSIGN(T, l); TEST_ASSIGN(T, move(l)); TEST_ASSIGN(T, cl); TEST_ASSIGN(T, move(cl)); TEST_ASSIGN(T, lr); TEST_ASSIGN(T, move(lr)); TEST_ASSIGN(T, clr); TEST_ASSIGN(T, move(clr)); TEST_ASSIGN(T, rr); TEST_ASSIGN(T, move(rr)); TEST_ASSIGN(T, crr); TEST_ASSIGN(T, move(crr)); TEST_ASSIGN(T, Fcprv()); TEST_ASSIGN(T, Fprv()); TEST_ASSIGN(T, fr); TEST_ASSIGN(T, move(fr)); TEST_ASSIGN(const T, 8); TEST_ASSIGN(const T, T()); TEST_ASSIGN(const T, l); TEST_ASSIGN(const T, move(l)); TEST_ASSIGN(const T, cl); TEST_ASSIGN(const T, move(cl)); TEST_ASSIGN(const T, lr); TEST_ASSIGN(const T, move(lr)); TEST_ASSIGN(const T, clr); TEST_ASSIGN(const T, move(clr)); TEST_ASSIGN(const T, rr); TEST_ASSIGN(const T, move(rr)); TEST_ASSIGN(const T, crr); TEST_ASSIGN(const T, move(crr)); TEST_ASSIGN(const T, Fcprv()); TEST_ASSIGN(const T, Fprv()); TEST_ASSIGN(const T, fr); TEST_ASSIGN(const T, move(fr)); //TEST_ASSIGN(T&, 8); //TEST_ASSIGN(T&, T()); TEST_ASSIGN(T&, l); //TEST_ASSIGN(T&, move(l)); //TEST_ASSIGN(T&, cl); //TEST_ASSIGN(T&, move(cl)); TEST_ASSIGN(T&, lr); //TEST_ASSIGN(T&, move(lr)); //TEST_ASSIGN(T&, clr); //TEST_ASSIGN(T&, move(clr)); //TEST_ASSIGN(T&, rr); //TEST_ASSIGN(T&, move(rr)); //TEST_ASSIGN(T&, crr); //TEST_ASSIGN(T&, move(crr)); //TEST_ASSIGN(T&, Fcprv()); //TEST_ASSIGN(T&, Fprv()); TEST_ASSIGN(T&, fr); //TEST_ASSIGN(T&, move(fr)); TEST_ASSIGN(const T&, 8); TEST_ASSIGN(const T&, T()); TEST_ASSIGN(const T&, l); TEST_ASSIGN(const T&, move(l)); TEST_ASSIGN(const T&, cl); TEST_ASSIGN(const T&, move(cl)); TEST_ASSIGN(const T&, lr); TEST_ASSIGN(const T&, move(lr)); TEST_ASSIGN(const T&, clr); TEST_ASSIGN(const T&, move(clr)); TEST_ASSIGN(const T&, rr); TEST_ASSIGN(const T&, move(rr)); TEST_ASSIGN(const T&, crr); TEST_ASSIGN(const T&, move(crr)); TEST_ASSIGN(const T&, Fcprv()); TEST_ASSIGN(const T&, Fprv()); TEST_ASSIGN(const T&, fr); TEST_ASSIGN(const T&, move(fr)); TEST_ASSIGN(T&&, 8); TEST_ASSIGN(T&&, T()); //TEST_ASSIGN(T&&, l); TEST_ASSIGN(T&&, move(l)); //TEST_ASSIGN(T&&, cl); //TEST_ASSIGN(T&&, move(cl)); //TEST_ASSIGN(T&&, lr); TEST_ASSIGN(T&&, move(lr)); //TEST_ASSIGN(T&&, clr); //TEST_ASSIGN(T&&, move(clr)); //TEST_ASSIGN(T&&, rr); TEST_ASSIGN(T&&, move(rr)); //TEST_ASSIGN(T&&, crr); //TEST_ASSIGN(T&&, move(crr)); //TEST_ASSIGN(T&&, Fcprv()); TEST_ASSIGN(T&&, Fprv()); //TEST_ASSIGN(T&&, fr); TEST_ASSIGN(T&&, move(fr)); TEST_ASSIGN(const T&&, 8); TEST_ASSIGN(const T&&, T()); //TEST_ASSIGN(const T&&, l); TEST_ASSIGN(const T&&, move(l)); //TEST_ASSIGN(const T&&, cl); TEST_ASSIGN(const T&&, move(cl)); //TEST_ASSIGN(const T&&, lr); TEST_ASSIGN(const T&&, move(lr)); //TEST_ASSIGN(const T&&, clr); TEST_ASSIGN(const T&&, move(clr)); //TEST_ASSIGN(const T&&, rr); TEST_ASSIGN(const T&&, move(rr)); //TEST_ASSIGN(const T&&, crr); TEST_ASSIGN(const T&&, move(crr)); TEST_ASSIGN(const T&&, Fcprv()); TEST_ASSIGN(const T&&, Fprv()); //TEST_ASSIGN(const T&&, fr); TEST_ASSIGN(const T&&, move(fr)); //TEST_ASSIGN(auto&, T{8}); //TEST_ASSIGN(auto&, T()); TEST_ASSIGN(auto&, l); //TEST_ASSIGN(auto&, move(l)); TEST_ASSIGN(auto&, cl); TEST_ASSIGN(auto&, move(cl)); TEST_ASSIGN(auto&, lr); //TEST_ASSIGN(auto&, move(lr)); TEST_ASSIGN(auto&, clr); TEST_ASSIGN(auto&, move(clr)); TEST_ASSIGN(auto&, rr); //TEST_ASSIGN(auto&, move(rr)); TEST_ASSIGN(auto&, crr); TEST_ASSIGN(auto&, move(crr)); TEST_ASSIGN(auto&, Fcprv()); //TEST_ASSIGN(auto&, Fprv()); TEST_ASSIGN(auto&, fr); //TEST_ASSIGN(auto&, move(fr)); TEST_ASSIGN(const auto&, T{8}); TEST_ASSIGN(const auto&, T()); TEST_ASSIGN(const auto&, l); TEST_ASSIGN(const auto&, move(l)); TEST_ASSIGN(const auto&, cl); TEST_ASSIGN(const auto&, move(cl)); TEST_ASSIGN(const auto&, lr); TEST_ASSIGN(const auto&, move(lr)); TEST_ASSIGN(const auto&, clr); TEST_ASSIGN(const auto&, move(clr)); TEST_ASSIGN(const auto&, rr); TEST_ASSIGN(const auto&, move(rr)); TEST_ASSIGN(const auto&, crr); TEST_ASSIGN(const auto&, move(crr)); TEST_ASSIGN(const auto&, Fcprv()); TEST_ASSIGN(const auto&, Fprv()); TEST_ASSIGN(const auto&, fr); TEST_ASSIGN(const auto&, move(fr)); TEST_ASSIGN(auto&&, T{8}); TEST_ASSIGN(auto&&, T()); TEST_ASSIGN(auto&&, l); TEST_ASSIGN(auto&&, move(l)); TEST_ASSIGN(auto&&, cl); TEST_ASSIGN(auto&&, move(cl)); TEST_ASSIGN(auto&&, lr); TEST_ASSIGN(auto&&, move(lr)); TEST_ASSIGN(auto&&, clr); TEST_ASSIGN(auto&&, move(clr)); TEST_ASSIGN(auto&&, rr); TEST_ASSIGN(auto&&, move(rr)); TEST_ASSIGN(auto&&, crr); TEST_ASSIGN(auto&&, move(crr)); TEST_ASSIGN(auto&&, Fcprv()); TEST_ASSIGN(auto&&, Fprv()); TEST_ASSIGN(auto&&, fr); TEST_ASSIGN(auto&&, move(fr)); TEST_ASSIGN(const auto&&, T{8}); TEST_ASSIGN(const auto&&, T()); //TEST_ASSIGN(const auto&&, l); TEST_ASSIGN(const auto&&, move(l)); //TEST_ASSIGN(const auto&&, cl); TEST_ASSIGN(const auto&&, move(cl)); //TEST_ASSIGN(const auto&&, lr); TEST_ASSIGN(const auto&&, move(lr)); //TEST_ASSIGN(const auto&&, clr); TEST_ASSIGN(const auto&&, move(clr)); //TEST_ASSIGN(const auto&&, rr); TEST_ASSIGN(const auto&&, move(rr)); //TEST_ASSIGN(const auto&&, crr); TEST_ASSIGN(const auto&&, move(crr)); TEST_ASSIGN(const auto&&, Fcprv()); TEST_ASSIGN(const auto&&, Fprv()); //TEST_ASSIGN(const auto&&, fr); TEST_ASSIGN(const auto&&, move(fr)); cout << endl; const int twidth = 9; cout << left << setw(twidth) << "From:"; for (const auto& lto : tos) { cout << left << setw(twidth) << lto; } cout << endl; for (const auto& lfrom : froms) { cout << left << setw(twidth) << lfrom; for (const auto& lto : tos) { if (!res.count(lfrom) || !res[lfrom].count(lto)) { cout << left << setw(twidth) << "-"; } else if (res[lfrom][lto].empty()) { cout << left << setw(twidth) << "+"; } else { cout << left << setw(twidth) << res[lfrom][lto]; } } cout << endl; } cout << endl; } int main() { test_conversion(); cout << endl; return 0; } /* Output: Dc Dc Dc Dc Dc T = 8: Pc 8-D T = T(): Dc 1-D T = l: Cc 1-D T = move(l): Mc 1-D T = cl: Cc 1-D T = move(cl): Cc 1-D T = lr: Cc 0-D T = move(lr): Mc 0-D T = clr: Cc 0-D T = move(clr): Cc 0-D T = rr: Cc 1-D T = move(rr): Mc 1-D T = crr: Cc 1-D T = move(crr): Cc 1-D T = Fcprv(): Dc 1-D T = Fprv(): Dc 1-D T = fr: Cc 1-D T = move(fr): Mc 1-D const T = 8: Pc 8-D const T = T(): Dc 1-D const T = l: Cc 0-D const T = move(l): Mc 0-D const T = cl: Cc 1-D const T = move(cl): Cc 1-D const T = lr: Cc 0-D const T = move(lr): Mc 0-D const T = clr: Cc 0-D const T = move(clr): Cc 0-D const T = rr: Cc 0-D const T = move(rr): Mc 0-D const T = crr: Cc 1-D const T = move(crr): Cc 1-D const T = Fcprv(): Dc 1-D const T = Fprv(): Dc 1-D const T = fr: Cc 0-D const T = move(fr): Mc 0-D T& = l: 0- T& = lr: 0- T& = fr: 0- const T& = 8: Pc 8-D const T& = T(): Dc 1-D const T& = l: 0- const T& = move(l): 0- const T& = cl: 1- const T& = move(cl): 1- const T& = lr: 0- const T& = move(lr): 0- const T& = clr: 0- const T& = move(clr): 0- const T& = rr: 0- const T& = move(rr): 0- const T& = crr: 1- const T& = move(crr): 1- const T& = Fcprv(): Dc 1-D const T& = Fprv(): Dc 1-D const T& = fr: 0- const T& = move(fr): 0- T&& = 8: Pc 8-D T&& = T(): Dc 1-D T&& = move(l): 0- T&& = move(lr): 0- T&& = move(rr): 0- T&& = Fprv(): Dc 1-D T&& = move(fr): 0- const T&& = 8: Pc 8-D const T&& = T(): Dc 1-D const T&& = move(l): 0- const T&& = move(cl): 1- const T&& = move(lr): 0- const T&& = move(clr): 0- const T&& = move(rr): 0- const T&& = move(crr): 1- const T&& = Fcprv(): Dc 1-D const T&& = Fprv(): Dc 1-D const T&& = move(fr): 0- auto& = l: 0- auto& = cl: 1- auto& = move(cl): 1- auto& = lr: 0- auto& = clr: 0- auto& = move(clr): 0- auto& = rr: 0- auto& = crr: 1- auto& = move(crr): 1- auto& = Fcprv(): Dc 1-D auto& = fr: 0- const auto& = T{8}: Pc 8-D const auto& = T(): Dc 1-D const auto& = l: 0- const auto& = move(l): 0- const auto& = cl: 1- const auto& = move(cl): 1- const auto& = lr: 0- const auto& = move(lr): 0- const auto& = clr: 0- const auto& = move(clr): 0- const auto& = rr: 0- const auto& = move(rr): 0- const auto& = crr: 1- const auto& = move(crr): 1- const auto& = Fcprv(): Dc 1-D const auto& = Fprv(): Dc 1-D const auto& = fr: 0- const auto& = move(fr): 0- auto&& = T{8}: Pc 8-D auto&& = T(): Dc 1-D auto&& = l: 0- auto&& = move(l): 0- auto&& = cl: 1- auto&& = move(cl): 1- auto&& = lr: 0- auto&& = move(lr): 0- auto&& = clr: 0- auto&& = move(clr): 0- auto&& = rr: 0- auto&& = move(rr): 0- auto&& = crr: 1- auto&& = move(crr): 1- auto&& = Fcprv(): Dc 1-D auto&& = Fprv(): Dc 1-D auto&& = fr: 0- auto&& = move(fr): 0- const auto&& = T{8}: Pc 8-D const auto&& = T(): Dc 1-D const auto&& = move(l): 0- const auto&& = move(cl): 1- const auto&& = move(lr): 0- const auto&& = move(clr): 0- const auto&& = move(rr): 0- const auto&& = move(crr): 1- const auto&& = Fcprv(): Dc 1-D const auto&& = Fprv(): Dc 1-D const auto&& = move(fr): 0- From: T const T T& const T& T&& const T&&auto& const auto&auto&& const auto&& 8 PcD PcD - PcD PcD PcD - - - - T() DcD DcD - DcD DcD DcD - DcD DcD DcD l CcD CcD + + - - + + + - move(l) McD McD - + + + - + + + cl CcD CcD - + - - + + + - move(cl) CcD CcD - + - + + + + + lr CcD CcD + + - - + + + - move(lr) McD McD - + + + - + + + clr CcD CcD - + - - + + + - move(clr)CcD CcD - + - + + + + + rr CcD CcD - + - - + + + - move(rr) McD McD - + + + - + + + crr CcD CcD - + - - + + + - move(crr)CcD CcD - + - + + + + + Fcprv() DcD DcD - DcD - DcD DcD DcD DcD DcD Fprv() DcD DcD - DcD DcD DcD - DcD DcD DcD fr CcD CcD + + - - + + + - move(fr) McD McD - + + + - + + + T{8} - - - - - - - PcD PcD PcD DDDDD */ 

用临时对象初始化常量引用


C ++允许使用临时对象初始化常量引用。 在这种情况下,将延长临时对象的寿命。 范例:


 struct T { int i = 1; }; const T& t = T(); cout << ti; 

但是,此生存期扩展仅在创建临时对象的块末尾有效。 因此,如果在构造函数中用临时对象初始化了类的常量引用成员,则临时对象将在构造函数的结尾处被破坏,并且引用将继续指向被破坏的对象,这是未定义的行为:


 class A { public: // Will not compile: value-initialization of reference type //A() : t() {} const T& t; }; class B { public: // Will compile in some compilers, but temporary object will be destructed at the end of constructor B() : t(T()) { cout << "In constructor: " << ti << endl; } const T& t; }; class C { public: // Will compile, but temporary object will be destructed at the end of constructor // Address sanitizer will show the problem C() : t(std::move(T())) { cout << "In constructor: " << ti << endl; } const T& t; }; C c; cout << "C: " << cti << endl; 

没有地址清理器,该程序将输出一些垃圾,而使用地址清理器,将显示错误。 因此,不应使用C ++功能或应谨慎使用。


友情链接:
参考初始化
对临时对象的常量引用
关于将const引用绑定到临时对象的子对象




转到第2部分

Source: https://habr.com/ru/post/zh-CN479342/


All Articles