zoukankan      html  css  js  c++  java
  • 【C++ Primer 第13章】6.对象移动

    右值引用

    左值和右值

    (1)两者区别:

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

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

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

    (2)右值的分类

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

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

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

      

    标准库的 move 函数

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

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

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

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

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

      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)_基本概念

  • 相关阅读:
    gcc 编译器常用的命令行参数一览
    linux下源代码分析和阅读工具比较
    Linux系统——C/C++开发工具及环境搭建
    GDB调试——经验总结
    gdb调试的艺术——Debug技巧
    命令__cp、scp(Secure Copy)
    常用shell脚本命令
    命令__查找、替换、删除
    UltraEdit 删除空行
    命令__shell数字-字符串比较
  • 原文地址:https://www.cnblogs.com/sunbines/p/9015924.html
Copyright © 2011-2022 走看看