C++面试题目_移动语义、右值引用

右值引用和移动语义

C++中所有的值都必然属于左值、右值二者之一。
左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象。所有的具名变量或者对象都是左值,而右值不具名。
很难得到左值和右值的真正定义,但是有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。

1.左值引用, 使用 T&, 只能绑定左值
2.右值引用, 使用 T&&, 只能绑定右值
3.常量左值, 使用 const T&, 既可以绑定左值又可以绑定右值
4.已命名的右值引用,编译器会认为是个左值
5.编译器有返回值优化,但不要过于依赖
  1. 为什么要有移动语义?

    int main()
    {
        vector<MyString> vecStr;
        vecStr.reserve(1000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000,因为vector是2倍增长的
        for(int i=0;i<1000;i++){
            vecStr.push_back(MyString("hello"));
        }
    }
    

    我们可以看到上面得代码中,不仅调用了1000次构造函数,还调用了1000次的赋值构造函数。这样会是多余的,造成了内存浪费,如果可以直接右值存进vector里面,就会加快内存申请和释放的时间。move语句就能实现将右值转为左值。
    要实现移动语义要增加两个函数:

    移动语义就是将右值当作左值用。

    #include <iostream>
    #include <cstring>
    #include <vector>
    using namespace std;
    
    class MyString
    {
    public:
        static size_t CCtor; //统计调用拷贝构造函数的次数
        static size_t MCtor; //统计调用移动构造函数的次数
        static size_t CAsgn; //统计调用拷贝赋值函数的次数
        static size_t MAsgn; //统计调用移动赋值函数的次数
    
    public:
    
    // 构造函数
    MyString(const char* cstr=0){
        if (cstr) {
            m_data = new char[strlen(cstr)+1];
            strcpy(m_data, cstr);
        }
        else {
            m_data = new char[1];
            *m_data = '\0';
        }
    }
    
    // 拷贝构造函数
    MyString(const MyString& str) {
       CCtor ++;
       m_data = new char[ strlen(str.m_data) + 1 ];
       strcpy(m_data, str.m_data);
    }
    // 移动构造函数
    MyString(MyString&& str) noexcept
       :m_data(str.m_data) {
       MCtor ++;
       str.m_data = nullptr; //不再指向之前的资源了
    }
    
    // 拷贝赋值函数 =号重载
    MyString& operator=(const MyString& str){
       CAsgn ++;
       if (this == &str) // 避免自我赋值!!
          return *this;
    
       delete[] m_data;
       m_data = new char[ strlen(str.m_data) + 1 ];
       strcpy(m_data, str.m_data);
       return *this;
    }
    
    // 移动赋值函数 =号重载
    MyString& operator=(MyString&& str) noexcept{
        MAsgn ++;
        if (this == &str) // 避免自我赋值!!
            return *this;
    
       delete[] m_data;
       m_data = str.m_data;
       str.m_data = nullptr; //不再指向之前的资源了
       return *this;
    }
    
    ~MyString() {
       delete[] m_data;
    }
    
    char* get_c_str() const { return m_data; }
    private:
        char* m_data;
    };
    
    size_t MyString::CCtor = 0;
    size_t MyString::MCtor = 0;
    size_t MyString::CAsgn = 0;
    size_t MyString::MAsgn = 0;
    int main()
    {
        vector<MyString> vecStr;
        vecStr.reserve(1000); //先分配好1000个空间
        for(int i=0;i<1000;i++){
            vecStr.push_back(MyString("hello"));
        }
        cout << "CCtor = " << MyString::CCtor << endl;
        cout << "MCtor = " << MyString::MCtor << endl;
        cout << "CAsgn = " << MyString::CAsgn << endl;
        cout << "MAsgn = " << MyString::MAsgn << endl;
    }
    
    /* 结果
    CCtor = 0
    MCtor = 1000
    CAsgn = 0
    MAsgn = 0
    */
    
  2. emplace_back减少内存拷贝和移动

    我们之前使用vector一般都喜欢用push_back(),由上文可知容易发生无谓的拷贝,解决办法是为自己的类增加移动拷贝和赋值函数,但其实还有更简单的办法!就是使用emplace_back()替换push_back(),如下面的例子:

    #include <iostream>
    #include <cstring>
    #include <vector>
    using namespace std;
    
    class A {
    public:
        A(int i){
    //        cout << "A()" << endl;
            str = to_string(i);
        }
        ~A(){}
        A(const A& other): str(other.str){
            cout << "A&" << endl;
        }
    
    public:
        string str;
    };
    
    int main()
    {
        vector<A> vec;
        vec.reserve(10);
        for(int i=0;i<10;i++){
            vec.push_back(A(i)); //调用了10次拷贝构造函数
            //vec.emplace_back(i);  
            //一次拷贝构造函数都没有调用过
        }
        for(int i=0;i<10;i++)
            cout << vec[i].str << endl;
    }
    

    可以看到效果是明显的,虽然没有测试时间,但是确实可以减少拷贝。emplace_back()可以直接通过构造函数的参数构造对象,但前提是要有对应的构造函数。
    对于map和set,可以使用emplace()。基本上emplace_back()对应push_bakc(), emplce()对应insert()。
    移动语义对swap()函数的影响也很大,之前实现swap可能需要三次内存拷贝,而有了移动语义后,就可以实现高性能的交换函数了。

    template <typename T>
    void swap(T& a, T& b)
    {
        T tmp(std::move(a));
        a = std::move(b);
        b = std::move(tmp);
    }
    

    如果T是可移动的,那么整个操作会很高效,如果不可移动,那么就和普通的交换函数是一样的,不会发生什么错误,很安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值