【C++ Primer 第13章】6.对象移动

摘要:
右值是指左值和右值(1):① 左值:可以从表达式中获取地址或命名对象/变量。通常,它指的是表达式结束后仍然存在的持久对象。② 正确的值:不能将地址或匿名对象作为表达式。通常,它指的是表达式末尾不再存在的临时对象。结论:一般来说,左值表达式表示对象的身份,而右值表达式表示一个对象的值。(2) 权利价值分类① 终止值(xvalue,eXpiringvalue):指生命周期即将结束的值,通常遵循正确的值

右值引用

左值和右值

(1)两者区别:

  ①左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。

  ②右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。

总结:一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。 

(2)右值的分类

  ①将亡值(xvalue,eXpiring value):指生命期即将结束的值,一般是跟右值引用相关的表达式,这样表达式通常是将要被移动的对象,如返回类型为T&&的函数返回值(如std::move)、经类型转换为右值引用的对象(如static_cast<T&&>(obj))、xvalue类对象的成员访问表达式也是一个xvalue(如Test().memberdata,注意Test()是个临时对象)

  ②纯右值(prvalue, PureRvalue):按值返回的临时对象运算表达式产生的临时变对象原始字面量lambda表达式等。

(3)C++11中的表达式

【C++ Primer 第13章】6.对象移动第1张

  

标准库的 move 函数

• 虽然不能将一个右值直接绑定到一个左值上,但可以显式地将一个左值转换为对应的右值引用类型。我们可以通过调用一个名为 move的新标准库函数来获得绑定到左值上的引用。头文件utility。

int &&rr3 = std::move(rr1); //ok

• 我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。

 移动构造函数和移动赋值运算符

与拷贝函数不同,移动构函数不分配任何新的内存,它接管给定StrVec中的内存,在接管内存之后, 它将给的对象中的指针置为nullptr,这样就完成了从给定对象的移动操作,此对象将继续存在。

【C++ Primer 第13章】6.对象移动第2张【C++ Primer 第13章】6.对象移动第3张
  1 #include<iostream>
  2 #include<string>
  3 #include<memory>
  4 using namespace std;
  5 
  6 class StrVec {
  7 public:
  8     StrVec(): elements(nullptr), first_free(nullptr), cap(nullptr) {}
  9     StrVec(const StrVec &);
 10     StrVec& operator=(const StrVec&);
 11     ~StrVec() { free(); };
 12 
 13     StrVec(StrVec &&s) noexcept;
 14     StrVec& opearator=(StrVec &&rhs) noexcept;
 15 
 16     void push_back (const string&);
 17     size_t size() const       { return first_free - elements; }
 18     size_t capacity() const   { return cap - elements; }
 19     string *begin() const     { return elements; }
 20     string *end() const       { return first_free; }
 21 
 22 private:
 23     static allocator<string> alloc;
 24     void chk_n_alloc() { if (size() == capacity()) reallocate(); }
 25     pair<string*, string*> alloc_n_copy(const string*, const string *);
 26     void free();
 27     void reallocate();
 28     string *elements;    //指向数组首元素的指针
 29     string *first_free;  //指向数组第一个空闲元素的指针
 30     string *cap;         //指向数组尾后位置的指针
 31 };
 32 
 33 StrVec::StrVec(const StrVec &s)
 34 {
 35     auto newdata = alloc_n_copy(s.begin(), s.end());
 36     elements = newdata.first;
 37     first_free = cap = newdata.second;
 38 }
 39 
 40 StrVec& StrVec::operator=(const StrVec &rhs)
 41 {
 42     auto data = alloc_n_copy(rhs.begin(), rhs.end());
 43     free();
 44     elements = data.first;
 45     first_free = cap = newdata.second;
 46     return *this;
 47 }
 48 
 49 void StrVec::free()
 50 {
 51     if (elements)
 52     {
 53         for (auto p = first_free; p != elements; )
 54             alloc.destroy(--p);
 55         alloc.deallocate(elements, cap - elements);
 56     }
 57 }
 58 
 59 pair<string *, string *> StrVec::alloc_n_copy(const string *b, const string *e)
 60 {
 61     auto data = alloc.allocate(e - b);
 62     return { data, uninitialized_copy(b, e, data) };
 63 }
 64 
 65 void StrVec::push_back(const string& s)
 66 {
 67     chk_n_alloc();
 68     alloc.construct(first_free++, s);
 69 }
 70 
 71 void StrVec::reallocate()
 72 {
 73     auto newcapacity = size() ? 2 * size() : 1;
 74     auto newdata = alloc.allocate(newcapacity);
 75     auto dest = newdata;
 76     auto elem = elements;  //原对象的elements指针
 77     for (size_t i = 0; i != size(); ++i)
 78         alloc.construct(dest++, std::move(*elem++));
 79     free();
 80     elements = newdata;
 81     first_free = dest;
 82     cap = elements + newcapacity;
 83 }
 84 
 85 StrVec::StrVec(StrVec &&s) noexcept
 86 : elements(s.elements), first_free(s.first_free), cap(s.cap)
 87 {
 88     s.elements = s.first_free = cap = nulllptr;
 89 }
 90 
 91 StrVec& StrVec::StrVec(StrVec &&s) noexcept
 92 {
 93     if (this = &s)
 94     {
 95         free();
 96         elemens = rhs.elements;
 97         first_free = .frhsirst_free;
 98         cap = rhs.cap;
 99         rhs.elements = srhs.first_free = rhs.cap = nullptr;
100     }
101     return *this;
102 }
View Code

• 与拷贝操作不同,编译器根本不会为某些类合成移动操作,特别是,如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或析构函数、编译器根本就不会为它合成移动构造函数。

• 如果一个类有一个可用的拷贝构造函数而没有移动构造函数,则其对象是通过拷贝构造函数来“移动”的,拷贝赋值运算符和移动赋值运算符的情况类似。

拷贝并交换赋值运算符和移动操作

右值引用和成员函数

区分移动和拷贝的重载函数通常有一个版本接受一个const T&, 而另一个版本接受一个T&&。

作为一个具体例子,我们将为StrVec类定义另一个版本的push_back:

 1 class StrVec {
 2 public:
 3     void push_back(const std::string&);  //拷贝元素
 4     void push_back(std:: string&&)       //移动元素
 5     // 其他成员的定义, 如前
 6 };
 7 
 8 void StrVec::push_back(const string &s)
 9 {
10     chk_n_alloc();  //确保有空间容纳新的元素
11     //在first_free指向的元素中构造s的一个副本
12     alloc.construct(first_free++, s);
13 }
14 
15 void StrVec::push_back(string &&s)
16 {
17     chk_n_alloc();  // 如果需要的话为StrVec重新分配内存
18     alloc.construct(first_free++, std::move(s));
19 }

 

右值和左值引用成员函数

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Foo {
 5 public:
 6     Foo & operator=(const Foo&) &; //只能向可修改的左值赋值
 7     Foo anotherMem() const &;      //正确,const 限定符在前
 8 
 9     Foo gg() const &&;   //this 也可以指向一个右值                        
10 };
11 
12 Foo& Foo::operator=(const Foo &rhs) &  //引用限定符和const一样必须同时出现在声明和定义中
13 {
14     //执行将rhs赋予本对象所需的工作
15     return *this;
16 }
17 
18 Foo& retFoo()  //返回一个左值
19 {
20     return *(new Foo());
21 }
22 
23 Foo retVal()    //返回一个右值
24 {
25     return Foo();
26 }
27 
28 int main(void)
29 {
30     Foo i, j;
31     i = j;
32     retFoo() = j;    //正确,retFoo() 返回一个左值
33     i = retVal();    //正确,我们可以将一个右值作为赋值运算符右侧运算对象
34     return 0;
35 }

重载和引用函数

 1 #include <iostream>
 2 #include <vector>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 class Foo{
 7 public:
 8     Foo sorted() &&;        //可用于改变的右值
 9     Foo sorted() const &;   //可用于任何类型的Foo
10     // Foo sorted();        //错误,当有两个以上同名且同参数列表的成员函数时,
11     
12     // 要么全(指这些同名且同参数列表的成员函数)都加引用限定符(只要有就行, 不论右值还是左值引用),要么都不加引用限定符
13     Foo sorted(int);//正确,参数列表不同
14 
15 private:
16     vector<int> data;
17 };
18 
19 Foo Foo::sorted() &&   // 本对象为右值,因此可以原址排序
20 {
21     sort(data.begin(), data.end());
22     return *this;
23 }
24 
25 Foo Foo::sorted() const &   // 本对象是const或是一个左值,哪种情况我们都不能原址排序
26 
27 {
28     Foo ret(*this);                           // 拷贝一个副本
29     sort(ret.data.begin(), ret.data.end());   // 副本排序
30     return ret;                               // 返回副本
31 }
32 
33 Foo& retFoo()    //返回一个左值
34 {
35     return *(new Foo());
36 }
37 
38 Foo retVal()     //返回一个右值
39 {
40     return Foo();
41 }
42 
43 int main(void)
44 {
45     retVal().sorted();    //retVal() 返回一个右值,调用 Foo::sorted() &&
46     retFoo().sorted();    //retFoo() 返回一个左值,调用 Foo::sorted() const &
47     return 0;
48 }

 参考资料

【1】右值引用(1)_基本概念

免责声明:文章转载自《【C++ Primer 第13章】6.对象移动》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇zabbix企业应用之bind dns监控(转)肉鸡是什么?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

springboot mybatis自定义枚举enum转换

原文链接:https://blog.csdn.net/u014527058/article/details/62883573 一、概述 在利用Spring进行Web后台开发时,经常会遇到枚举类型的绑定问题。一般情况下,如果Spring接收到的参数值为字符串类型,Spring会根据枚举的值与传入的字符串进行对应。假设有如下枚举 清单1:枚举定义 public...

设计模式--观察者模式初探和java Observable模式

初步认识观察者模式  观察者模式又称为发布/订阅(Publish/Subscribe)模式,因此我们可以用报纸期刊的订阅来形象的说明:     报社方负责出版报纸.     你订阅了该报社的报纸,那么只要报社发布了新报纸,就会通知你,或发到你手上.     如果你不想再读报纸,可以取消订阅,这样,报社发布了新报纸就不会再通知你.   理解其实以上的概念,就...

C#WebBrowser控件使用教程与技巧收集

C#WebBrowser控件使用教程与技巧收集--苏飞收集 先来看看常用的方法 Navigate(string urlString):浏览urlString表示的网址 Navigate(System.Uri url):浏览url表示的网址 Navigate(string urlString, string targetFrameName, byt...

C# 调用Java的WebService的3种方式

C# 调用WebService的3种方式 :直接调用、根据wsdl生成webservice的.cs文件及生成dll调用、动态调用 关于soapheader调用,可以参考 C#调用Java的WebService添加SOAPHeader验证1.问题描述 调用的Java的webservice string Invoke(string func, string r...

spring mvc 的jpa JpaRepository数据层 访问方式汇总

本文转载:http://perfy315.iteye.com/blog/1460226和http://jishiweili.iteye.com/blog/2088265 AppleFramework在数据访问控制层采用了Spring Data作为这一层的解决方案,下面就对Spring Data相关知识作一个较为详细的描述。1.Spring Data所解决的...

ASP.NET Core SignalR (七):考虑设计向后兼容的SignalR API

此为系列文章,对MSDN ASP.NET Core SignalR 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解。 使用自定义参数对象来确保向后兼容         向SignalR 中心 方法添加参数(要么是服务端方法,要么是客户端方法)是一个重大的变化。这就意味着老的 服务端/客户端在不带有预期个数的参数进行...