当前位置: 首页 > news >正文

曹县住房和城乡建设部网站芜湖县建设局网站

曹县住房和城乡建设部网站,芜湖县建设局网站,wordpress阿里百秀5.4,阿里巴巴网站建设教程文章目录1. 左值和右值表达式1.1 概念1.2 左值和右值2. 左值引用和右值引用2.1 相互引用2.2 示例代码2.3 左值引用使用场景缺点2.4 右值引用和移动语义小结2.5 移动赋值2.6 右值引用的其他使用场景右值引用版本的插入函数3. 完美转发3.1 万能引用3.2 如何实现完美转发3.3 完美转… 文章目录1. 左值和右值表达式1.1 概念1.2 左值和右值2. 左值引用和右值引用2.1 相互引用2.2 示例代码2.3 左值引用使用场景缺点2.4 右值引用和移动语义小结2.5 移动赋值2.6 右值引用的其他使用场景右值引用版本的插入函数3. 完美转发3.1 万能引用3.2 如何实现完美转发3.3 完美转发的应用3.4 补充3.4 补充1. 左值和右值表达式 首先要明确左值和右值都是表达式。值value是无法进一步求值的表达式例如表达式“11”就不是一个值因为它可以被简化为表达式“3”不能继续被化简因此“3”是一个值。 根据左值和右值的特性可以简单地用是否能取到地址来判断类型 左值l-value能被取地址右值r-value不能被取到地址。 1.1 概念 左值具有确定的、可以被获得的内存地址。这意味着左值可以是变量也可以是对指向特定内存地址的指针解引用的结果。 在C语言中“左值”最初表示可以被赋值的对象也就是赋值运算符左侧的对象但是随着const关键字的引入这类对象就被称作“可更改的左值”。在C中左值和右值是表达式的分类也就是说一个表达式非左值即右值。 C11将左值性lvalueness扩充为更复杂的值类别value category包含左值lvalue纯右值prvalue和临终值xvalue三个基本分类fundamental classification一个表达式必然是三者之一。 C11标准引入了右值引用数据类型与移动语义因而左值与右值的定义发生了很大变化。右值引用变量绑定到右值上延长了右值对应的临时对象的生存期。移动语义把临时对象的内容移动move到左值对象上。 C语言引入了const限定的对象C语言只有const限定的内置类型。const对象可以取地址但是不能被赋值而一些右值对象也可以出现在赋值号的左边被赋值。 因此截至C03标准把具有标识的表达式规定为左值不具有标识的表达式规定为右值。因而名字、指针、引用等是左值是命名对象具有确定的内存地址字面量、临时对象等为右值右值仅在创建它的表达式中可以被访问。函数名字是左值在C语言中规定它既不是左值也不是右值数组名是常量左值但是在大多数表达式中函数名字与数组名字自动隐式转换为右值。右值的生存期短暂所以需要用左值去捕捉右值。把右值复制到左值上是常见操作。例如 表达式x2声明了一个变量x并将x赋值为2那么表达式x的值是2并且是一个左值2就是一个右值实现了用左值去捕捉右值即将右值复制到左值上。 表达式11在执行时计算机生成一个整数值2它是一个临时值由于程序没有明确指定这个2如何在计算机中存储所以这个表达式产生一个右值。 总的来说 左值表示一个占据内存中某个可识别的位置也就是一个地址的对象。右值即非左值也就一个不表示内存中某个可识别位置的对象的表达式。 再例如 int a; a 1;赋值操作需要左操作数是一个左值。a 是一个有内存位置的对象因此它是左值。 1 a; (a 1) 2;这样的写法是错误的因为常量1和表达式a1都不是左值即它们是右值。因为它们都是表达式的临时值临时值是没有可识别的内存位置原因是临时值作为计算过程的中间值是被保存在寄存器中的。那么上面这种写法就是将它们赋值到了一个不存在的位置这是不被允许的。 1.2 左值和右值 左值表达式 可以被取地址可以被修改除了const修饰的左值例如变量名或解引用的指针。可以在赋值运算符的两侧。 例如 int* pa new int(1); // 指针 int a *pa; // 被解引用的指针 int b 2; // 变量名 const int c 3; // 不可被修改右值表达式非左即右 不能被取地址也不能被修改例如字面常量、表达式的返回值、函数的非左值引用返回值。只能在赋值运算符的右侧。 例如 int func(int a, int b) {return a - b; } int main() {1;int a 1, b 2;a b;func(a, b);return 0; }右值表达式只能在赋值运算符的右侧也就是说它不能被赋值只能将它赋值给其他变量 // 错误写法 //1 11; //x y 2; //func(a, b) 3;// 正确写法 int x 1; int c a b; int d func(a, b);通过示例可以知道右值和左值的区别 右值的本质就是常量值或临时值如上例中的1就是字面常量xy和函数的返回值就是一个临时值。 临时值一般作为计算过程的中间值因此它被保存在寄存器中没有地址。而常量值存储在数据段中。 上面的函数的返回值是一个临时变量如果返回一个引用就是左值了。 关于左值和右值最后再用一个例子理解 左值是一张纸它里面的值就是字右值是单纯的字。 如上例中的a b就是12那么c a b就是c3。用c这个空间存储3这个值c就是纸是左值3就是字是右值。假如dc就是把c这张纸中的字抄到d这张纸上那么c就是右值而这与d上原来的内容是无关的。 详见AlseinX的回答链接https://www.zhihu.com/question/380792984/answer/1099475972 小结在C中左值和右值是两种表达式的分类。左值是指可以出现在赋值号 的左边或右边的表达式它有一个明确的内存地址可以被引用或修改。右值是指只能出现在赋值号 的右边的表达式它没有一个明确的内存地址不能被引用或修改。右值一般是常量、临时对象或函数返回值。 2. 左值引用和右值引用 C中引用就是给变量起别名C11以后将C11之前的引用称为左值引用进而引入C11中最强有力的特性右值引用。 左值引用 语法左值引用和之前的引用一样给左值取别名 int main() {int* pa new int(1);int a *pa;int b 2;const int c 3;// 左值引用起别名int* Pa pa;int A a;int B b;const int C c;return 0; }右值引用 语法右值引用就是给右值取别名通过声明 int func(int a, int b) {return a - b; } int main() {1;int a 1, b 2;a b;func(a, b);// 右值引用起别名int x 1;int c a b;int d func(a, b);return 0; }这段代码的核心思想是使用右值引用来优化程序性能通过创建右值引用变量x、c和d可以避免在每次调用函数func时创建临时变量。 右值本身是不能被取地址的但是右值被起了别名以后会被存储到特定位置并且可以被修改可以使用const关键字限制。 x 2; // 修改引用指向的内容 cout x endl; cout x endl; // 取出引用指向内容的地址输出 2 00FEF8602.1 相互引用 左值引用可以引用右值吗 左值引用不能直接引用右值原因是左值可以被修改而右值不行。这就类似非const变量能否接收普通变量涉及到权限问题。权限只能被缩小或平移不能被放大。 int main() {int a 1, b 2;// int c a b; // 错误const int c a b; // 正确// c是左值引用return 0; }这里我们可以想到在这之前我们总是有这样的习惯函数的参数类型不仅是引用而且还是被const修饰的认为“为保证安全性避免被修改”是没错的但从更深层次看来加上const也是为了参数能够兼容左值引用和右值引用。 右值引用可以引用左值吗 右值引用只能引用右值。右值引用可以引用move以后的左值。 move函数是C11新增的函数被move后的左值能够赋值给右值引用例如 int main() {int a 1, b 2;int c move(a b);return 0; }这段代码的核心思想是使用move函数将ab的值转移到右值引用c中从而实现ab的值被转移而不是拷贝。原本cb是一个左值结果move函数的处理后就可以被右值引用。 C中的move函数是一种将对象从一个位置移动到另一个位置的操作它可以用来移动拥有资源的对象以避免复制和析构的开销而不会拷贝对象的内容。move函数是一个类似于强制转换的函数它可以将一个右值转换为一个左值从而使得右值的内存资源可以被释放从而节省内存空间。 补充move 函数的定义如下 templateclass _Ty inline typename remove_reference_Ty::type move(_Ty _Arg) _NOEXCEPT {//forward _Arg as movablereturn ((typename remove_reference_Ty::type)_Arg); }注意 _Arg 参数的类型不是右值引用而是万能引用。万能引用跟右值引用的形式一样但是右值引用需要是确定的类型。被move的左值的状态是未知的它有可能已经被破坏了。 2.2 示例代码 C中右值引用的使用场景主要用于移动语义或者说移动语义利用右值引用实现资源转移即从一个对象将其内容移动到另一个对象而不需要执行复制操作。 右值引用的意义在于提供了一种更加灵活的方式来处理右值在函数调用中右值引用可以用来避免拷贝构造函数的调用从而提高程序的效率在容器中右值引用可以用来实现移动语义从而更加高效地移动容器中的数据。 例如当将一个临时变量赋值给一个普通变量时会发生复制操作如果这个对象很大带来的开销是不可忽略的而使用右值引用可以避免该复制操作从而提高程序性能。 用一个简单的string类示例它只实现了拷贝构造函数和赋值运算符重载函数等基本成员函数并分别在它们内部增加提示语句。 #include iostream #include cstring using namespace std; namespace xy {class string{public:// 构造函数string(const char* str ){_size strlen(str);_capacity _size;_str new char[_capacity 1];strcpy(_str, str);}void swap(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// *拷贝构造函数string(const string s):_str(nullptr), _size(0), _capacity(0){cout string(const string s) -- 深拷贝 endl;string tmp(s._str);swap(tmp);}// *赋值运算符重载string operator(const string s){cout string operator(const string s) -- 深拷贝 endl;string tmp(s);swap(tmp);return *this;}void push_back(char ch){if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;_str[_size 1] \0;_size;}// 运算符重载string operator(char ch){push_back(ch);return *this;}void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strncpy(tmp, _str, _size 1);delete[] _str;_str tmp;_capacity n;}}// 其他接口...private:char* _str;size_t _size;size_t _capacity;}; }注意这个string类是深拷贝。且为了方便稍后和库中的string一起测试用一个命名空间包了起来。 理解了拷贝的方式就能理解左值引用和右值引用的作用 区分左值和右值的目的是优化程序的性能和内存管理。左值一般是有名字的变量它们占用一定的内存空间可以被重复使用或修改。右值一般是临时的表达式结果它们在使用后就会被销毁不能被重复使用或修改。如果能够将右值转移给左值就可以避免不必要的拷贝和内存分配提高程序的运行效率。C11 之后引入了右值引用和移动语义move使得这种转移成为可能。 2.3 左值引用 使用场景 既然引用本身的价值就是减少拷贝那么左值引用的优点也是类似的 做参数减少拷贝提高效率做输出型参数。做返回值减少拷贝提高效率引用返回可以修改返回的对象如operator[]。 输出型参数就像鱼钩一样它作为参数被传入函数执行完毕后便能取出。OJ常常将它用于尤其是C语言返回多个返回值。 然而在“减少拷贝”方面左值引用并未覆盖所有情况。 例如to_string这个接口就是传值返回原因是它的返回值是一个类型它有析构函数当跳出函数的作用域时析构函数会被自动调用返回值就是一个局部对象了。除此之外还有使用容器实现的二维数组从语言角度上string和vector都是一个类型class都有自己的构造和析构函数。 注意C11之前的所有引用从C11的角度看都是左值引用此处讨论的也是如此。 而且C98不支持返回二维数组的引用只能返回指针。同样地只支持返回string的拷贝而不能返回string的引用。 例如C98难以解决上面这两种情况 string to_string(int val); // to_string原型 vectorvectorint func(int num) // 返回值是二维数组例如杨辉三角如何避免返回值拷贝提高效率 不难想到可以用输出型参数解决这个问题 void to_string(int val, string str); void func(int num, vectorvectorint vv);当然这样能解决问题但没人会这样做因为它不符合习惯。使用输出型参数也不那么优雅。 在全局中新增一个to_string接口但是不用实现内部功能只需要保证to_string的返回值是一个临时对象即可 xy::string to_string(int val) {xy::string str;// ...return str; }测试 int main() {xy::string str to_string(1);return 0; }输出 string(const string s) -- 深拷贝从写法上看to_string内部会调用一次xy::string拷贝构造函数main中创建str对象也会调用一次拷贝构造函数结果只打印了一次拷贝构造函数。原因是编译器vs2019优化了一次拷贝。如果使用gcc编译器可以使用指令关闭拷贝构造优化 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O46GFcp0-1678369024210)(null)] 回到程序本身str to_string(1)的本意就是让1这个值初始化str从以往的经验来看这个1就是一个临时值经过编译器优化以后是不会调用构造函数的。 VS优化的只会打印一次它忽略了to_string的临时对象。原因是不优化的时候它在创建main函数的栈帧的时候扩大一点以供这个临时变量存放然后从to_string拷贝到这个地方再拷贝到main函数栈帧中的ret上这样就拷贝了两次被VS优化了中间的环节。 缺点 左值引用虽然能在很多情况下避免拷贝带来的开销但是左值引用在函数的返回值中不能起到作用原因是函数内部的变量都是局部变量出了函数的作用域就会被销毁不论是否是引用都无法在函数外部取到。 这就是右值引用存在的意义。 2.4 右值引用和移动语义 移动语义是C11提供的一种优化技术它可以将一个对象的资源如内存、文件句柄等从一个对象转移到另一个对象而不需要复制或者销毁资源。 移动语义可以利用右值引用来实现通过重载移动构造函数和移动赋值运算符来定义对象如何被移动。移动语义可以提高程序的性能避免不必要的拷贝和内存分配。 简单来说移动语义通过移动构造函数实现。移动构造函数就是一个资本家它的参数是右值引用类型这个构造函数想“右值既然是临时值在它消亡之前不如利用一下”在内部会将右值的资源直接转移到自己身上用来构造自己。 因此就左值引用不能用于返回值这一问题可以在string类中写一个移动构造函数在函数内部将传入的右值的资源通过swap函数转移到自己身上增加语句以提示 namespace xy {class string{public://移动构造string(string s):_str(nullptr), _size(0), _capacity(0){cout string(string s) -- 移动构造 endl;swap(s);}private:char* _str;size_t _size;size_t _capacity;}; }这样就能解决左值引用在函数返回值中的问题。 右值引用和移动语义之间的关系 右值引用是一种特殊的引用类型它可以绑定到一个临时对象或者一个即将被销毁的对象从而表示这个对象可以被移动而不需要拷贝。移动语义需要右值引用来实现因为右值引用可以区分出哪些对象是可以被安全地移动的而不会影响其他地方的使用。通过重载移动构造函数和移动赋值运算符我们可以定义当一个对象被右值引用绑定时如何将它的资源转移到另一个对象中从而避免不必要的拷贝和内存分配。 移动构造函数和拷贝构造函数都是用来利用一个已有对象构造出一个新的对象的但是它们的区别如下 移动构造函数的参数是一个右值引用而拷贝构造函数的参数是一个左值引用。在没有增加移动构造之前由于拷贝构造采用的是 const 左值引用接收参数因此无论拷贝构造对象时传入的是左值还是右值都会调用拷贝构造函数增加移动构造之后由于移动构造采用的是右值引用接收参数因此如果拷贝构造对象时传入的是右值那么就会调用移动构造函数最匹配原则。移动构造函数可以直接将已有对象的资源转移到新对象中而不需要分配新的空间或者复制数据。这样可以提高性能降低成本。例如 string 的拷贝构造函数是深拷贝而移动构造函数中只需要调用 swap 函数进行资源转移因此移动构造的代价比拷贝构造的成本小。拷贝构造函数会保留已有对象的资源和数据而移动构造函数会使已有对象失去资源和数据。因此在使用移动构造函数后已有对象可能处于不可预期的状态。 注意 虽然 to_string 中返回值是一个左值但是 string 对象在当前函数调用结束后就会立即被销毁这种即将被消耗的值叫做 “将亡值”比如匿名对象也是 “将亡值”。既然 “将亡值” 马上就要被销毁了那还不如物尽其用最后再利用一下。因此编译器在识别 “将亡值” 时会将它识别为右值这样就可以匹配到参数类型为右值引用的移动构造函数。 编译器的优化 当一个函数在返回局部对象时会先用这个局部对象拷贝构造出一个临时对象然后再用这个临时对象来拷贝构造接收返回值的对象 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rgGdgCC-1678369024150)(null)] 如上所说C11之前编译器会将这两次拷贝构造优化为1次拷贝构造 以上仅为VS编译器做的优化即使它原本是为了解决C11之前的问题但是对C11后的语法仍然有作用 如果没有编译器优化为1次拷贝C11仍然会调用两次移动构造函数。 不同接收返回值的方式对拷贝次数带来的影响 如果是用一个已经定义的对象来接收返回值就相当于拷贝了operator[]操作符拷贝此时虽然是两次拷贝构造编译器也无法再优化下去了 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqwdgW39-1678369024117)(null)] 会先用这个局部对象拷贝构造出一个临时对象然后再调用赋值运算符重载函数将这个临时对象赋值给接收函数返回值的对象。 编译器没有对这种情况进行优化因此在 C11 之前有深拷贝的类就会有两次深拷贝因为深拷贝的类的赋值运算符重载函数都以深拷贝的方式实现。但在深拷贝的类中引入 C11 的移动构造后这里仍然需要再调用一次赋值运算符重载函数进行深拷贝因此深拷贝的类不仅需要实现移动构造还需要实现移动赋值。 注意 对于有返回局部对象的函数即使只是调用函数不接收返回值也会存在一次拷贝构造或移动构造因为返回值不论如何都存在当函数结束后函数内的局部对象都会被销毁所以就算不接收函数的返回值也会调用一次拷贝构造或移动构造创建临时对象。 小结 右值引用和移动语义是C11引入的新特性它们可以提高程序的性能和效率避免不必要的内存分配和拷贝。 右值引用是一种特殊的引用类型它只能绑定到临时对象或没有名称的变量也就是右值。右值引用使用符号来声明例如int r 10;。右值引用可以让编译器区分左值和右值从而选择合适的构造函数或赋值运算符。 移动语义是一种利用右值引用来实现对象所有权转移的机制。当一个对象被移动后它的资源例如指针、内存等被转移到另一个对象上而自身变成一个空对象只能被析构。这样可以避免资源的复制和释放提高效率。移动语义需要定义移动构造函数和移动赋值运算符并使用std::move()函数来将左值转换为右值引用。 何时使用右值引用和移动语义 当你需要返回一个大型或占用资源的对象时你可以使用右值引用作为返回类型让编译器执行强制或可选的复制/移动省略copy/move elision从而避免创建临时对象。当你需要传递一个大型或占用资源的对象作为参数时你可以使用右值引用作为参数类型并在调用时使用std::move()函数将左值转换为右值引用从而让编译器调用移动构造函数或移动赋值运算符当你需要定义一个自己管理资源例如指针、内存等的类时你可以定义移动构造函数和移动赋值运算符并在其中实现资源所有权的转移逻辑。 关于内存泄露 移动语义本身不会导致内存泄露的问题但是如果使用不当可能会出现一些意想不到的后果。例如如果你使用std::move函数来转移一个对象的数据那么你必须保证被转移的对象之后不再被使用否则可能会访问到空指针或者无效数据。另外如果你定义了自己的移动构造函数或者移动赋值运算符那么你必须确保在转移资源的同时把被转移对象的成员指针置为空并且正确处理异常情况。否则可能会出现资源泄露或者重复释放的问题。 2.5 移动赋值 移动赋值运算符是一种重载的赋值运算符它的参数是自身类的右值引用返回值是自身类的左值引用。它可以实现对象之间的资源所有权转移而不是复制。 例如如果你有一个字符串类它有一个指向动态分配内存的指针成员变量。当你用一个临时字符串对象或一个即将被销毁的字符串对象来给另一个字符串对象赋值时你可以使用移动赋值运算符来将源对象的指针直接赋给目标对象并将源对象置为空。这样就避免了内存分配和复制的开销并保证了源对象在析构时不会释放已经转移的资源。 移动赋值运算符需要使用noexcept关键字非必要来指定不抛出任何异常并使用std::move()函数来将左值转换为右值引用。 以下是一个简单的示例 namespace xy {class string {public:// 移动赋值运算符string operator(string s) noexcept {cout string operator(string s) -- 移动赋值 endl;swap(s);return *this;}private:char* _str;size_t _size;size_t _capacity;}; }移动赋值运算符和赋值运算符都是用于给类类型的对象赋值的特殊成员函数但是它们有一些区别 移动赋值运算符的参数是一个右值引用它可以接受临时对象或者被std::move转换为右值的对象它不会分配新的内存而是直接移动数据成员。赋值运算符的参数是一个左值引用或者一个非引用形参它可以接受左值或者右值它会执行按位复制或者调用复制构造函数或者移动构造函数来生成临时对象。 对此STL也根据C11新增了移动构造函数例如string类。 移动构造 string (string str) noexcept;移动赋值 string operator (string str) noexcept;2.6 右值引用的其他使用场景 右值引用版本的插入函数 除了移动构造和移动赋值之外STL在C11之后还给插入接口增加了右值引用版本。 例如vector的push_back [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ri7XNZwT-1678369024182)(null)] 如果vector容器中存储的是string对象可以有以下几种插入方式 int main() {vectorxy::string v;xy::string s(1);v.push_back(s); // 调用string的拷贝构造v.push_back(2); // 调用string的移动构造v.push_back(xy::string(3)); // 调用string的移动构造v.push_back(std::move(s)); // 调用string的移动构造return 0; }STL容器中右值引用的插入接口是C11新增的一种方式它可以提高性能和效率。 右值引用的插入接口可以接受临时对象或者被std::move转换为右值的对象它不会创建新的对象而是直接移动数据到容器中。右值引用的插入接口可以避免不必要的拷贝和内存分配可以实现数据控制权的转移。 3. 完美转发 C11中的完美转发是一种技术它可以保持参数的值属性不变即左值还是左值右值还是右值。 完美转发可以避免不必要的拷贝和内存分配提高性能和效率。完美转发需要使用右值引用和std::forward函数来实现。完美转发在变长模板中非常有用因为它可以处理不同类型和数量的参数。 C11中的forward函数的原型是一个用于实现完美转发的模板函数。它有两种重载版本一种接收左值引用一种接收右值引用。它的作用是根据传入参数的左右值属性来返回相应的左值或右值从而避免不必要的拷贝。 3.1 万能引用 万能引用是一种C11中的特殊引用它可以绑定到左值或右值而不需要指定类型。它的作用是实现完美转发即保持参数的原始值类别。 万能引用可以让一个引用类型的参数既能绑定到右值也能绑定到左值。万能引用的形式是T其中T是一个模板类型或者auto类型。万能引用的本质是根据实参的类型来推导T的类型从而实现右值引用或者左值引用。例如 templatetypename T void f(T param); // param是万能引用int x 10; f(x); // T被推导为intparam是左值引用 f(20); // T被推导为intparam是右值引用auto var x; // var是万能引用下面重载了4个func函数根据参数是左值和右值const与否一共有4个func重载函数 #include iostream using namespace std;void Func(int x) {cout 左值引用 endl; } void Func(const int x) {cout const 左值引用 endl; } void Func(int x) {cout 右值引用 endl; } void Func(const int x) {cout const 右值引用 endl; }templateclass T void PerfectForward(T t) {Func(t); } int main() {int a 1;PerfectForward(a); // 左值PerfectForward(move(a)); // 右值const int b 2;PerfectForward(b); // const 左值PerfectForward(move(b)); // const 右值return 0; }输出 左值引用 左值引用 const 左值引用 const 左值引用结果却与设想的不同传入不同类型的参数就是想让编译器匹配不同参数类型的重载函数但是最终都调用了const左值引用。原因是右值被引用后会导致右值被存储到特定位置此时这个右值可以被取到地址且可以被修改所以在PerfectForward函数中调用Func函数时会将t识别成左值。也就是说右值在经过一次参数传递后其属性会退化成左值如果想要在这个过程中保持右值的属性就需要进行完美转发。 代码没有实现完美转发而是将所有的参数都视为左值。当传递一个右值给PerfectForward时它会被绑定到T类型的万能引用上但是在PerfectForward函数内部t仍然是一个左值因为它有一个名字。所以将t传递给Func时它会匹配到左值引用类型的重载版本。同理当传递一个const右值给PerfectForward时它也会被视为const左值。 3.2 如何实现完美转发 完美转发使得一个函数可以将参数原封不动地传递给另一个函数保持其值类别。它的实现需要使用std::forward函数和万能引用。例如 #include iostream using namespace std;// 一个普通的函数 void func(int a) {cout left endl; }// 一个重载的函数 void func(int a) {cout right endl; }// 一个模板函数使用万能引用 templatetypename T void func1(T a) {// 使用std::forward进行完美转发func(forwardT(a)); }int main() {int x 10; // x是左值func1(x); // 输出leftfunc1(20); // 输出right }完美转发的目的是保持参数的原始值类别即左值还是右值const还是非const。但是在3.1的这段代码中PerfectForward函数只是简单地将t传递给Func函数而不使用std::forward进行类型转换。这样会导致t在PerfectForward函数内部被视为左值 从而调用错误的重载版本。例如当传递一个右值给PerfectForward时它应该调用Func(int x)或Func(const int x)但实际上却调用了Func(int x)或Func(const int x)。 为了解决这个问题需要在PerfectForward函数中使用std::forwardT(t)来将t转换为正确的类型 。这样就可以实现完美转发了。修改后的代码如下 #include iostream using namespace std;void Func(int x) {cout 左值引用 endl; } void Func(const int x) {cout const 左值引用 endl; } void Func(int x) {cout 右值引用 endl; } void Func(const int x) {cout const 右值引用 endl; }templateclass T void PerfectForward(T t) {// 使用std::forward进行类型转换Func(std::forwardT(t)); } int main() {int a 1;PerfectForward(a); // 左值PerfectForward(move(a)); // 右值const int b 2;PerfectForward(b); // const 左值PerfectForward(move(b)); // const 右值return 0; }输出 左值引用 右值引用 const 左值引用 const 右值引用std::forward可以根据t的原始类型来返回相应的左值或右值引用。这样就可以保持参数的原始值类别不变并调用正确的重载版本了。 3.3 完美转发的应用 下面是实现的简易的vector类其中只有push_back接口和insert接口且有完美转发的功能。代码如下 #include iostream #include stdlib.h using namespace std; namespace xy {templatetypename Tclass vector{public:vector() // 默认构造函数: data(nullptr), size(0), capacity(){}~vector() // 析构函数{delete[] data;}void init(){data (T*) malloc(sizeof(T) * 100);}void push_back(const T x) // 在末尾添加一个元素拷贝{insert(size, x);}void push_back(T x) // 在末尾添加一个元素移动{insert(size, std::forwardT(x));}void insert(size_t pos, const T x) // 在指定位置插入一个元素拷贝{for (size_t i size; i pos; i--){data[i] data[i - 1]; // 将pos之后的元素后移一位}data[pos] x; // 拷贝元素到pos位置并更新sizesize;}void insert(size_t pos, T x) // 在指定位置插入一个元素移动{for (size_t i size; i pos; i--){data[i] std::forwardT(data[i - 1]);}data[pos] std::forwardT(x);size;}private:T *data; // 指向动态数组的指针size_t size; // 已使用的空间size_t capacity; // 总容量}; }测试vector的元素类型是之前自定义的string类型注意这个string类在2.5中已经增加了移动赋值重载函数分别传入左值引用和右值引用的参数 int main() {xy::vectorxy::string v;v.init();v.push_back(1); // 右值引用cout 右值引用↑----------------左值引用↓ endl;xy::string s1(3); // 左值引用v.push_back(s1);return 0; }输出 string operator(string s) -- 移动赋值 string(const string s) -- 深拷贝 右值引用↑----------------左值引用↓ string operator(const string s) -- 深拷贝 string(const string s) -- 深拷贝使用完美转发后右值引用版本的push_back函数接收到右值后其属性不会退化到左值所以调用的insert函数还是右值引用版本匹配到string类的移动赋值版本的operator重载函数。 同理对于左值引用的参数最终只会匹配到左值引用版本的insert函数对应string类原来的operator重载函数。 注意 想要保证右值的属性一直不发生变化需要在每次右值被传参时都进行完美转发事实上STL也是这么做的。例如vector每次扩容后的资源转移其中的赋值操作就必须使用std::forward()或std::move()。 想保证右值的属性不发生变化有以下几种方法 使用 const 关键字来修饰右值引用例如 const int r 10;。这样就可以防止对 r 的修改。使用 std::move() 函数来将一个左值转换为右值然后再绑定到右值引用上例如 int x 10; int r std::move(x);。这样就可以避免对 x 的修改影响到 r。使用 std::forward() 函数来实现完美转发即根据参数的类型自动选择左值引用或右值引用。这样就可以保持参数的原始属性不变。 push_back和insert接口中的参数T已经被推断为右值引用了因为实例化vector对象时的参数类型就已经确定了。 3.4 补充 测试所实现的简易vector与STL之间最大的区别就是开辟空间方式的不同STL首先通过空间配置器获取内存在申请到内存之后不会立刻调用构造函数初始化而是用定位new用左值或右值对申请的内存空间初始化此时调用的函数是拷贝构造或移动构造。 简易实现的vector使用的是malloc开辟空间申请到空间以后会立刻调用构造函数初始化对象所以在上面的例子中会调用string原有的operator而不是string的拷贝构造函数深拷贝或移动构造函数资源转移。 ck函数接收到右值后其属性不会退化到左值所以调用的insert函数还是右值引用版本匹配到string类的移动赋值版本的operator重载函数。 同理对于左值引用的参数最终只会匹配到左值引用版本的insert函数对应string类原来的operator重载函数。 注意 想要保证右值的属性一直不发生变化需要在每次右值被传参时都进行完美转发事实上STL也是这么做的。例如vector每次扩容后的资源转移其中的赋值操作就必须使用std::forward()或std::move()。 想保证右值的属性不发生变化有以下几种方法 使用 const 关键字来修饰右值引用例如 const int r 10;。这样就可以防止对 r 的修改。使用 std::move() 函数来将一个左值转换为右值然后再绑定到右值引用上例如 int x 10; int r std::move(x);。这样就可以避免对 x 的修改影响到 r。使用 std::forward() 函数来实现完美转发即根据参数的类型自动选择左值引用或右值引用。这样就可以保持参数的原始属性不变。 push_back和insert接口中的参数T已经被推断为右值引用了因为实例化vector对象时的参数类型就已经确定了。 3.4 补充 测试所实现的简易vector与STL之间最大的区别就是开辟空间方式的不同STL首先通过空间配置器获取内存在申请到内存之后不会立刻调用构造函数初始化而是用定位new用左值或右值对申请的内存空间初始化此时调用的函数是拷贝构造或移动构造。 简易实现的vector使用的是malloc开辟空间申请到空间以后会立刻调用构造函数初始化对象所以在上面的例子中会调用string原有的operator而不是string的拷贝构造函数深拷贝或移动构造函数资源转移。
http://www.hkea.cn/news/14262932/

相关文章:

  • 企业网站源码asp做公司网站需要准备什么资料
  • 青岛外贸网站建设哪家好网站html地图制作
  • 扬州网站建设网站婚庆网站模板下载
  • 天津网站建设首选 津坤科技传媒公司 网站开发
  • 一般可以建些什么种类的网站互联网做网站属于什么行业
  • 如何自己做搜索网站盘锦做网站哪家好
  • 融水做的比较好的网站有哪些天津网站优化公司价格
  • 手机端网站建设广告词seo排名工具有哪些
  • 中企动力网站建设精品案例网站优化培训如何优化
  • 泰安网站优化公司电商网站首页图片切换怎么做的
  • 手机网站怎么建Dedecms手机网站源码
  • 沈阳中联做网站关键词优化好
  • 综合网站推广的含义服务器做视频网站吗
  • 怎样做淘宝联盟的网站阳信住房和城乡建设厅网站
  • 网站关键词收费网络设计与规划实验报告
  • 中国最大的博客网站网站建设梦幻创意
  • 网站建设用啥系统好建立一个国外的网站
  • 做本地网站微信软文范例大全100
  • 杭州专业的网站制作成功案例wordpress贴代码
  • 仿美团版网站开发制作太原市建设工程安全监督站网站
  • 英文网站如何做网站代运营推广
  • 黄埔网站建设(信科网络)小公司企业简介300字
  • 成都建设规划局网站庆阳网站设计 贝壳下拉
  • 网站备案承若怎么写免费网站建设itcask
  • 因酷网站建设网站设计制作哪些
  • 网站建设按什么收费服务专业的网站制作服务
  • 郑州网站制作郑州网站制作百度快照优化排名推广
  • 怎样制作一个网站步骤wordpress 轻量级
  • 洛阳网站建设汉狮报价阿里云网站建设视频
  • 网站浮动代码自己服务器建设网站