C++11新特性之右值引用(&&)、移動語義(move)、完美轉換(forward)


1. 右值引用

  個人認為右值引用的目的主要是為了是減少內存拷貝,優化性能。

  比如下面的代碼:

String Fun()
{
    String str = "hello world";
    return str;
}

  str為臨時對象,然后調用Stringd的拷貝構造函數,將臨時對象的值賦值給String,這種拷貝是完全沒有必要的,如果堆內存很大,那么這個拷貝構造的代價會很大,帶來了額外的性能損耗

  為了避免鏈式對象的拷貝構造,我們可以使用右值引用拷貝的方式來實現:

MyString& operator=(MyString&& other)
    {
        cout << "MyString& operator=(const MyString&& other)" << endl;
        if (this != &other)
        {
            m_nLen = other.m_nLen;
            m_pData = other.m_pData;
            other.m_pData = NULL;
        }

        return *this;
    }

  上面的代碼只是進行了指針權限的轉移,而沒有額外的性能消耗。

1.1 使用右值引用實現MyString類

#include "stdio.h"
#include <string>
#include <iostream>
using namespace std;

class MyString
{
public:
    MyString() :m_pData(NULL), m_nLen(0)
    {
        cout << "MyString()" << endl;
    }
    MyString(const char *pStr)   // 允許隱式轉換
    {
        cout << "MyString(const char *pStr)" << endl;
        m_nLen = strlen(pStr);
        CopyData(pStr);
    }
    MyString(const MyString& other)
    {
        cout << "MyString(const MyString& other)" << endl;
        if (!other.m_pData)
        {
            m_nLen = other.m_nLen;
            DeleteData();
            CopyData(other.m_pData);
        }
    }
    MyString& operator=(const MyString& other)
    {
        cout << "MyString& operator=(const MyString& other)" << endl;
        if (this != &other)
        {
            m_nLen = other.m_nLen;
            DeleteData();
            CopyData(other.m_pData);
        }

        return *this;
    }

    MyString(MyString&& other)
    {
        cout << "MyString(MyString&& other)" << endl;
        m_nLen = other.m_nLen;
        m_pData = other.m_pData;
        other.m_pData = NULL;
    }

    MyString& operator=(MyString&& other)
    {
        cout << "MyString& operator=(const MyString&& other)" << endl;
        if (this != &other)
        {
            m_nLen = other.m_nLen;
            m_pData = other.m_pData;
            other.m_pData = NULL;
        }

        return *this;
    }

    ~MyString()
    {
        DeleteData();
    }

private:
    void CopyData(const char *pData)
    {
        if (pData)
        {
            m_pData = new char[m_nLen + 1];
            memcpy(m_pData, pData, m_nLen);
            m_pData[m_nLen] = '\0';
        }
    }

    void DeleteData()
    {
        if (m_pData != NULL)
        {
            delete[] m_pData;
            m_pData = NULL;
        }
    }

private:
    char *m_pData;
    size_t m_nLen;
};

MyString Fun()
{
    MyString str = "hello world";
    return str;
}
void main()
{
    MyString str1 = "hello";
    MyString str2(str1);
    MyString str3 = Fun();
}

1.2 右值引用總結

C++11中引入了右值引用和移動語義,可以避免無謂的復制,提高了程序的性能,右值引用標記為T&&。

(1)左值和右值是獨立於它們的類型,右值引用類型可能是左值也可能是右值

(2)auto&&或函數參數類型的自動推導的T&&是一個未定的引用類型,它可能是左值引用,也可能是右值引用,取決於初始化的值類型

(3)所有的右值引用疊加到右值引用上仍然是一個右值引用,其它引用疊加都為坐值引用,當T&&為模版參數時,輸入左值,它會變為左值引用,輸入右值則變為具名的右值引用

(4)編譯器會將已命名的右值引用視為左值,而將未命名的右值視為右值

2. move語義

  我們知道移動語義是通過右值引用來匹配臨時值的,那么,普通的左值是否也能借組移動語義來優化性能呢?C++11為了解決這個問題,提供了std::move()方法來將左值轉換為右值,從而方便應用移動語義。move是將對象的狀態或者所有權從一個對象轉移到另一個對象,只是轉義,沒有內存拷貝。

MyString str1 = "hello";
MyString str2(str1);
MyString str3 = Fun();
MyString str4 = move(str2);

3. forward

  forward將左值轉換為右值:

MyString str1 = "hello";
MyString str2(str1);
MyString str3 = Fun();
MyString str4 = move(str2);
MyString str5(forward<MyString>(str3));

4. 綜合示例

#include "stdio.h"
#include<iostream>

#include<vector>
using namespace std;

class A
{
public:
    A() :m_ptr(NULL), m_nSize(0){}
    A(int *ptr, int nSize)
    {
        m_nSize = nSize;
        m_ptr = new int[nSize];
        if (m_ptr)
        {
            memcpy(m_ptr, ptr, sizeof(sizeof(int) * nSize));
        }
    }
    A(const A& other)   // 拷貝構造函數實現深拷貝
    {
        m_nSize = other.m_nSize;
        if (other.m_ptr)
        {
            delete[] m_ptr;
            m_ptr = new int[m_nSize];
            memcpy(m_ptr, other.m_ptr, sizeof(sizeof(int)* m_nSize));
        }
        else
        {
            m_ptr = NULL;
        }
        cout << "A(const int &i)" << endl;
    }

    // 右值應用構造函數
    A(A &&other)    
    {
        m_ptr = NULL;
        m_nSize = other.m_nSize;
        if (other.m_ptr)
        {
            m_ptr = move(other.m_ptr);  // 移動語義
            other.m_ptr = NULL;
        }
    }

    ~A()
    {
        if (m_ptr)
        {
            delete[] m_ptr;
            m_ptr = NULL;
        }
    }

    void deleteptr()
    {
        if (m_ptr)
        {
            delete[] m_ptr;
            m_ptr = NULL;
        }
    }

    int *m_ptr;
    int m_nSize;
};

void main()
{
    int arr[] = { 1, 2, 3 };
    A a(arr, sizeof(arr)/sizeof(arr[0]));
    cout << "m_ptr in a Addr: 0x" << a.m_ptr << endl;
    A b(a);
    cout << "m_ptr in b Addr: 0x" << b.m_ptr << endl;
    b.deleteptr();
    A c(std::forward<A>(a));   // 完美轉換
    cout << "m_ptr in c Addr: 0x" << c.m_ptr << endl;
    c.deleteptr();

    vector<int> vect{ 1, 2, 3, 4, 5 };
    cout << "before move vect size: " << vect.size() << endl;

    vector<int> vect1 = move(vect);
    cout << "after move vect size: " << vect.size() << endl;
    cout << "new vect1 size: " << vect1.size() << endl;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM