C++语法|⭐️对象优化(三)|move移动语义和forward类型完美转发

案例引入

首先我们看一个案例。

我们使用自己定义的string类:

class CMyString {
public:
    CMyString(const char* str = nullptr) {
        cout << "CMyString(const char*)" << endl;
        if (str != nullptr) {
            mptr = new char[strlen(str) + 1];
            strcpy(mptr, str);
        } else {
            mptr = new char[1];
            *mptr = '\0';
        }
    }

    ~CMyString() {
        cout << "~CMyString()" << endl;
        delete[] mptr;
        mptr = nullptr;
    }
    CMyString(const CMyString& str) {
        cout << "CMyString(const CMyString&)" << endl;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
    }
    CMyString(CMyString &&str) {    //str引用的就是一个右值
        cout << "CMyString(CMyString &&str)" << endl;
        mptr = str.mptr;
        str.mptr = nullptr;
    }
    CMyString& operator=(const CMyString& str) {
        cout << "operator=(const CMyString&)" << endl;
        if (this == &str) {
            return *this;
        }
        delete[] mptr;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
        return *this;
    }
    CMyString& operator=(CMyString &&str) {
        cout << "operator=(CMyString&&)" << endl;
        if (this == &str)
            return *this;

        delete[] mptr;
        
        mptr = str.mptr;
        str.mptr = nullptr;
        return *this;
    }

    const char* c_str() const { return mptr; }
private:
    char* mptr;
    friend CMyString operator+(const CMyString &lhs,
                    const CMyString &rhs);
    friend ostream& operator<<(ostream &out, const CMyString &str);
};

CMyString operator+(const CMyString &lhs,
                    const CMyString &rhs){
    //char *ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
    CMyString tempStr;
    tempStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
    strcpy(tempStr.mptr, lhs.mptr);
    strcat(tempStr.mptr, rhs.mptr);
    //delete []ptmp;
    return tempStr;
    //return CMyString(ptmp);
}

ostream& operator<<(ostream &out, const CMyString &str) {
    out << str.mptr;
    return out;
}

现在我们做如下测试:

int main () {
    CMyString str1 = "aaa";

    vector<CMyString> vec;
    vec.reserve(10);

    cout << "-----------------------" << endl;
    vec.push_back(str1); //传了一个左值,引用的是普通的带左值引用的拷贝构造
    vec.push_back(CMyString("bbb")); //传了一个右值,引用的是一个带右值引用的拷贝构造
    cout << "-----------------------" << endl;

    return 0;
}

打印结果如下:

CMyString(const char*) //str1构造
-----------------------
CMyString(const CMyString&)//str1拷贝构造
CMyString(const char*)	//CMyString("bbb")是一个右值,底层先构造实例
CMyString(CMyString &&str)	//然后调用移动构造
~CMyString()	//释放CMyString("bbb"),但其实他的底层已经指向空了
-----------------------
~CMyString()
~CMyString()
~CMyString()

其实我们的移动构造是没有什么开销的,我们也是先构造出CMyString("bbb")的对象实例,然后把实例的资源直接给到vec的底层元素,这又是我们移动构造做的事情。随后的析构函数也没有什么开销,因为我们已经把CMyString("bbb")实例的底层指针置为空了。

所以不要认为我们把一个类对象传入容器的开销有多大,其实根本就不打,从C++11开始,push_back的底层就已经很高效了。
但是我们还是要问一个问题:
push_back是如何做到的呢?
move 移动语义和类型的完美转发

重新审视自己实现的vector类

在之前的文章中,我们自己实现了一个简单的vector,现在一起改造他!实现移动构造函数和类型的完美转发。

//以下代码是带有空间配置器、迭代器内置类型的vector版本
template <typename T, typename Alloc = Allocator<T> >
class vector {
public:
    vector(int size = 10) {
        //_first = new T[size];
        _first = _allocator.allocate(size);
        _last = _first;
        _end = _first + size;
    }
    ~vector() {
        //delete[] _first;
        for(T *p = _first; p != _last; ++p) {
            _allocator.destroy(p);  //把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first); //释放堆上的内存
        _first = _last = _end =nullptr;
    }
    vector(const vector<T> &rhs) {
        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; ++i) {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first + i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
    }
    vector<T>& operator=(const vector<T> &rhs) {
        if (this == rhs) return *this;
        
        //delete[]_first;
        for(T *p = _first; p != _last; ++p) {
            _allocator.destroy(p);  //把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first); //释放堆上的内存

        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; ++i) {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first + i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;

        return *this;
    }

    void push_back(const T &val) {
        if (full()) expand();
        //*_last++ = val; _last指针指向的内存构造一个值为val的对象
        _allocator.construct(_last, val);
        _last++;
    }

    void pop_back() {
        if (empty()) return ;
        //--_last; 不仅要把_last--,还需要析构删除的元素
        --_last;
        _allocator.destroy(_last);
    }

    T back() const {    //返回容器末尾元素的值
        return *(_last - 1);
    }

    bool full() const { return _last == _end; }
    bool empty() const { return _first == _last; }
    int size() const { return _last - _first; }

    T& operator[](int index) { // vec[2]
        if (index < 0 || index >= size()) {
            throw "OutOfRangeException";
        }
        return _first[index]; 
    }

    //迭代器
    class iterator {
    public:
        iterator(T *ptr = nullptr) : _ptr(ptr) {}
        bool operator!=(const iterator &it) const {
            return _ptr != it._ptr;
        }
        void operator++() {
            _ptr++;
        }
        T& operator*() { return *_ptr; }
        const T& operator*() const { return *_ptr; }
    private:
        T *_ptr;
    };
    //需要给容器提供begin和end方法
    iterator begin() { return iterator(_first); }
    iterator end() { return iterator(_last); }
    
private:  
    T *_first;  //指向数组起始的位置
    T *_last;  //指向数组中有效元素的后继位置
    T *_end;  //指向数组空间的后继位置
    Alloc _allocator; //定义容器的空间配置器对象

    void expand() { //容器的二倍扩容操作
        int size = _end - _first;
        //T *ptemp = new T[2 * size];
        T *ptemp = _allocator.allocate(2 * size);

        for (int i = 0; i < size; ++i) {
            //ptemp[i] = _first[i];
            _allocator.construct(ptemp + i, _first[i]);
        }
        //delete[] _first;
        for (T *p = _first; p != _last; ++p) {
            _allocator.destroy(p);
        }
        _allocator.deallocate(_first);
        _first = ptemp;
        _last = _first + size;
        _end = _first + 2 * size;
    }
};

在这个代码中,我们并没有实现带右值引用的拷贝构造和赋值构造,可以自行实现,我们在这里仅仅考虑push_back。

重载带右值引用的push_back

    void push_back(T &&val) {
        if (full()) expand();
        //*_last++ = val; _last指针指向的内存构造一个值为val的对象
        _allocator.construct(_last, val);
        _last++;
    }

首先右值引用的push_back和左值的一样。注意这里有一个_allocator.construct(_last, val);他是负责对象构造的,所以我们必须给它也提供一个右值版本。

首先分析之前我们实现的空间配置器,construct:

    void construct(T *p,  T &&val) {
        new (p) T(val);
    }

然后,首先我们需要明确,一个右值引用变量本身还是一个左值。尽管所以我们上面的右值版本的push_back(T &&val),函数体中val还是一个左值,所以我们这段_allocator.construct(_last, val);还是会调用左值版本的construct,所以应该:

_allocator.construct(_last, std::move(val));

使用std::move(),将一个左值引用类型强转成右值引用类型。
同理在construct函数中写:

new (p) T(std::move(val));

完成上述工作后,
现在打印结果如下:

CMyString(const char*)
-----------------------
CMyString(const CMyString&)
CMyString(const char*)
CMyString(CMyString &&str)
~CMyString()
-----------------------
~CMyString()
~CMyString()
~CMyString()

已经与标准库的vector打印一致了。

NOTE:
但是我们这里仍然有很多问题,我们尽管提供了相应方法的右值版本,但是由于语法规则,右值引用变量其实是一个左值,所以在函数体内仍然无法分辨出该变量是一个右值引用,我们不得不添加std::move移动语义来把变量转换为一个右值。
那么这种问题应该如何解决呢?

使用forword类型完美转发实现

    template<typename Ty>
    void push_back(Ty &&val) {
        if (full()) expand();

        _allocator.construct(_last, val);
        _last++;
    }

刚才实现的两个push_back全部不要来,我们使用一个模版函数来定义push_back,如上述。

如果我们传参是一个左值引用,就有Ty=CMyString&
从Ty &&val==> CMyString& &&val ==> CMyString &val!
参数被编译器推导为了左值!
如果我们传参是一个右值引用,就有Ty=CMyString&&
从Ty &&val==> CMyString&& &&val ==> CMyString &&val!
参数被编译器推导为了右值!

这是由于以下几点:

  • 引用折叠 &&&出现三个引用的概念叫引用折叠,是在C++11新出的
  • 函数模版的类型推演

通过一个函数模版,把我们之前写的两个push_back方法可以完美替换掉;

那么我们解决下一个问题:函数体内无法分清变量到底是一个左值还是右值,因为右值引用本质上还是左值。

//forward类型的完美转发:
_allocator.construct(_last, std::forward<Ty>(val));

std::forward可以根据我们参数val本身的定义,推导出我们的val到底是一个左值还是右值。如果是左值,他就会返回一个左值类型,就会调用construct的左值版本;如果是右值,他就会返回一个右值类型,调用construct的右值版本。

最后同理,我们把construct代码也重构一下:

    template<typename Ty>
    void construct(T *p,  Ty &&val) { //左值版本的构造
        new (p) T(std::forward<Ty>(val));
    }

一定要注意,我们定义的Ty只是来判断我们传进来的val是左值还是右值。

至此完成!
我们写了一个高效的push_back版本!

整体代码:

CMyString和MyVector代码

#include <iostream>
using namespace std;

class CMyString {
public:
    CMyString(const char* str = nullptr) {
        cout << "CMyString(const char*)" << endl;
        if (str != nullptr) {
            mptr = new char[strlen(str) + 1];
            strcpy(mptr, str);
        } else {
            mptr = new char[1];
            *mptr = '\0';
        }
    }

    ~CMyString() {
        cout << "~CMyString()" << endl;
        delete[] mptr;
        mptr = nullptr;
    }

    //带左值引用参数的拷贝构造
    CMyString(const CMyString& str) {
        cout << "CMyString(const CMyString&)" << endl;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
    }
    //带右值引用参数的拷贝构造
    CMyString(CMyString &&str) {    //str引用的就是一个右值
        cout << "CMyString(CMyString &&str)" << endl;
        mptr = str.mptr;
        str.mptr = nullptr;
    }

    //带左值引用参数的赋值重载函数
    CMyString& operator=(const CMyString& str) {
        cout << "operator=(const CMyString&)" << endl;
        if (this == &str) {
            return *this;
        }
        delete[] mptr;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
        return *this;
    }

    //带右值引用参数的赋值重载函数
    CMyString& operator=(CMyString &&str) {
        cout << "operator=(CMyString&&)" << endl;
        if (this == &str)
            return *this;

        delete[] mptr;
        
        mptr = str.mptr;
        str.mptr = nullptr;
        return *this;
    }

    const char* c_str() const { return mptr; }
private:
    char* mptr;

    friend CMyString operator+(const CMyString &lhs,
                    const CMyString &rhs);
    friend ostream& operator<<(ostream &out, const CMyString &str);
};

CMyString operator+(const CMyString &lhs,
                    const CMyString &rhs){
    //char *ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
    CMyString tempStr;
    tempStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
    strcpy(tempStr.mptr, lhs.mptr);
    strcat(tempStr.mptr, rhs.mptr);
    //delete []ptmp;
    return tempStr;
    //return CMyString(ptmp);
}

ostream& operator<<(ostream &out, const CMyString &str) {
    out << str.mptr;
    return out;
}

template<typename T>
struct Allocator {
    T* allocate(size_t size) {//只负责内存开辟
        return (T*)malloc(size * sizeof(T));
    }
    void deallocate(void *p) { // 负责内存释放
        free(p);
    } 
    // void construct(T *p, const T &val) {//复杂对象构造
    //     new (p) T(val);
    // }
    // void construct(T *p,  T &&val) { //左值版本的构造
    //     new (p) T(std::move(val));
    // }
    template<typename Ty>
    void construct(T *p,  Ty &&val) { //左值版本的构造
        new (p) T(std::forward<Ty>(val));
    }
    void destroy(T *p) { //负责对象析构
        p->~T();    // ~T()代表了T类型的析构函数
    }
};

template <typename T, typename Alloc = Allocator<T> >
class vector {
public:
    vector(int size = 10) {
        //_first = new T[size];
        _first = _allocator.allocate(size);
        _last = _first;
        _end = _first + size;
    }
    ~vector() {
        //delete[] _first;
        for(T *p = _first; p != _last; ++p) {
            _allocator.destroy(p);  //把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first); //释放堆上的内存
        _first = _last = _end =nullptr;
    }
    vector(const vector<T> &rhs) {
        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; ++i) {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first + i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
    }
    vector<T>& operator=(const vector<T> &rhs) {
        if (this == rhs) return *this;
        
        //delete[]_first;
        for(T *p = _first; p != _last; ++p) {
            _allocator.destroy(p);  //把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first); //释放堆上的内存

        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; ++i) {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first + i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;

        return *this;
    }

    void pop_back() {
        if (empty()) return ;
        //--_last; 不仅要把_last--,还需要析构删除的元素
        --_last;
        _allocator.destroy(_last);
    }

    T back() const {    //返回容器末尾元素的值
        return *(_last - 1);
    }

    bool full() const { return _last == _end; }
    bool empty() const { return _first == _last; }
    int size() const { return _last - _first; }

    T& operator[](int index) { // vec[2]
        if (index < 0 || index >= size()) {
            throw "OutOfRangeException";
        }
        return _first[index]; 
    }

    //迭代器
    class iterator {
    public:
        iterator(T *ptr = nullptr) : _ptr(ptr) {}
        bool operator!=(const iterator &it) const {
            return _ptr != it._ptr;
        }
        void operator++() {
            _ptr++;
        }
        T& operator*() { return *_ptr; }
        const T& operator*() const { return *_ptr; }
    private:
        T *_ptr;
    };
    //需要给容器提供begin和end方法
    iterator begin() { return iterator(_first); }
    iterator end() { return iterator(_last); }

    //
    /*
    void push_back(const T &val) {
        if (full()) expand();
        //*_last++ = val; _last指针指向的内存构造一个值为val的对象
        _allocator.construct(_last, val);
        _last++;
    }

    void push_back(T &&val) {
        if (full()) expand();

        _allocator.construct(_last, std::move(val));
        _last++;
    }*/

    template<typename Ty>
    void push_back(Ty &&val) {
        if (full()) expand();

        _allocator.construct(_last, std::forward<Ty>(val));
        _last++;
    }
    
private:  
    T *_first;  //指向数组起始的位置
    T *_last;  //指向数组中有效元素的后继位置
    T *_end;  //指向数组空间的后继位置
    Alloc _allocator; //定义容器的空间配置器对象

    void expand() { //容器的二倍扩容操作
        int size = _end - _first;
        //T *ptemp = new T[2 * size];
        T *ptemp = _allocator.allocate(2 * size);

        for (int i = 0; i < size; ++i) {
            //ptemp[i] = _first[i];
            _allocator.construct(ptemp + i, _first[i]);
        }
        //delete[] _first;
        for (T *p = _first; p != _last; ++p) {
            _allocator.destroy(p);
        }
        _allocator.deallocate(_first);
        _first = ptemp;
        _last = _first + size;
        _end = _first + 2 * size;
    }
};

测试代码如下:

int main () {
    CMyString str1 = "aaa";

    ::vector<CMyString> vec;

    cout << "-----------------------" << endl;
    vec.push_back(str1); //传了一个左值,引用的是普通的带左值引用的拷贝构造
    vec.push_back(CMyString("bbb")); //传了一个右值,引用的是一个带右值引用的拷贝构造
    cout << "-----------------------" << endl;
    return 0;
}
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值