C++性能優(yōu)化之二:右值引用

來龍去脈

在我項目里出牧,經(jīng)常會出現(xiàn)這樣一段代碼:

#define _C_S(x) String(x)
String str = _C_S("hello world");

這個代碼的運行機制實際上是這樣的:

String tmp("hello world");
String str = tmp;
tmp.~String();

構造函數(shù)生成臨時的tmp對象(申請內(nèi)存塊A存放”hello world”)穴肘,然后通過復制構造函數(shù),將tmp內(nèi)存里的內(nèi)容復制到str對象(str申請內(nèi)存塊B舔痕,接受從內(nèi)存塊A復制過來的字符串)评抚,然后tmp對象脫離作用域調用析構函數(shù)(第二行代碼結束,釋放內(nèi)存塊A)伯复。仔細分析下慨代,發(fā)現(xiàn)有冗余的內(nèi)存申請和釋放,這里實際上存在兩次內(nèi)存申請啸如,和一次內(nèi)存釋放鱼响,那是不是有辦法做到,只申請一次內(nèi)存就完成上述代碼组底。

答案是有的丈积,我們只需要把tmp對象的內(nèi)存“移動”到str中即可,這就是C++11的右值引用债鸡。(由于我們項目C++代碼的基礎容器都是自己維護的江滨,并沒有使用stl,因此會缺失很多新特性厌均,如C++11的右值引用)

左值右值的定義

首先說明右值引用之前唬滑,先解釋下C++里對于右值和左值的定義

當一個對象被用作右值的時候,用的是對象的值(內(nèi)容)棺弊;當對象被用作左值的時候晶密,用的是對象的身份(在內(nèi)存中的位置)模她。

概念有點抽象稻艰,舉幾個例子來看看

int a = 52; //a是左值
int b = a + c; //b是左值,a+c是左值
string c = string("hello") //c是左值侈净,string("hello")是右值

從上面代碼可以看出尊勿,其實左值和右值的根本區(qū)別在于能否獲取內(nèi)存地址僧凤,左值有自己的變量名和地址,而右值是函數(shù)返回的或者運算符計算得出的臨時對象元扔,出了作用域就會被析構躯保。

右值引用的應用

那么引入右值引用的目的是什么呢?很簡單澎语,合理規(guī)劃臨時對象的內(nèi)存使用途事。

如果沒有右值引用,像使用string這種有指針成員變量的臨時對象擅羞,去給左值做構造或者賦值時盯孙,就會存在多余的內(nèi)存申請和釋放,如果該指針指向的內(nèi)存塊很大祟滴,那么這種頻繁的臨時對象內(nèi)存的申請和釋放很容易導致內(nèi)存碎片和內(nèi)存尖峰振惰,進而影響性能。

下面代碼是以一個簡單的字符串String類為例垄懂,實現(xiàn)了String的復制構造函數(shù)和賦值運算符的右值引用版本骑晶,來說明右值引用的作用。

class String
{
public:
    //構造函數(shù)
    String();
    String(const char* str);
    
    //復制構造函數(shù)
    String(const String& str);
    
    //復制構造函數(shù)-右值引用
    String(String&& str);
    
    //賦值運算符函數(shù)
    String& operator=(const String& str);
    
    //賦值運算符函數(shù)-右值引用
    String& operator=(String&& str);
    
    //析構函數(shù)
    virtual ~String();
    
    //字符串反轉
    String reverse();
    
    void show(){cout << _pdata <<endl;};
private:
    size_t _len;
    char* _pdata;
};
String::String()
{
    _len = 0;
    _pdata = nullptr;
}
String::String(const char* str)
{
    _len = strlen(str);
    _pdata = new char[_len + 1];
    if (_pdata != nullptr)
    {
        memcpy(_pdata, str, _len);
        _pdata[_len] = '\0';
    }
}
String::String(const String& str)
{
    _len = str._len;
    _pdata = new char[_len + 1];
    if (_pdata != nullptr)
    {
        memcpy(_pdata, str._pdata, _len);
        _pdata[_len] = '\0';
    }
}
String::String(String&& str)
{
    _len = str._len;
    _pdata = str._pdata;
    str._len = 0;
    str._pdata = nullptr;
}
String& String::operator=(String&& str)
{
    if (_pdata)
    {
        delete [] _pdata;
        _pdata = nullptr;
        _len = 0;
    }
    _pdata = str._pdata;
    _len = str._len;
    str._len = 0;
    str._pdata = nullptr;
    return *this;
}
String& String::operator=(const String& str)
{
    if (&str != this)
    {
        if (_len > str._len)
        {
            memset(_pdata, 0, _len);
            _len = 0;
            memcpy(_pdata, str._pdata, str._len);
            _len = str._len;
            _pdata[_len] = '\0';
        }
        else if (_len == str._len)
        {
            memcpy(_pdata, str._pdata, str._len);
        }
        else
        {
            delete [] _pdata;
            _pdata = nullptr;
            
            _len = str._len;
            _pdata = new char[_len + 1];
            if (_pdata != nullptr)
            {
                memcpy(_pdata, str._pdata, _len);
                _pdata[_len] = '\0';
            }
        }
    }
    return *this;
}
String::~String()
{
    if (_pdata != nullptr)
    {
        delete [] _pdata;
        _pdata = nullptr;
        _len = 0;
    }
}
String String::reverse()
{
    String ret = _pdata;
    int sidx = 0;
    int eidx = (int)(ret._len - 1);
    while (sidx <= eidx) {
        char tmp = ret._pdata[sidx];
        ret._pdata[sidx] = ret._pdata[eidx];
        ret._pdata[eidx] = tmp;
        sidx++;
        eidx--;
    }
    return ret; //調用移動函數(shù)-右值引用
}
int main()
{
    String str = String("098"); //調用移動函數(shù)-右值引用
    String str2("110");
    str2 = String("098"); //調用移動函數(shù)-右值引用
    
    //str2Reverse的地址和reverse函數(shù)中ret變量的地址是一致的
    String str2Reverse = str2.reverse(); //調用移動函數(shù)-右值引用
    return 0;
}

注意到草慧,右值引用版本的復制構造和賦值運算符函數(shù)桶蛔,將臨時對象的內(nèi)存“移動”到了左值,從而避免了臨時對象的內(nèi)存浪費漫谷,提高了運行效率仔雷。

總結

將該特性移植到我們項目工程代碼后,內(nèi)存申請和調用頻次大幅減少舔示,虛存和cpu都有小幅下降碟婆,其實不僅僅是拷貝構造和賦值運算符存在臨時對象,所有其他用到這兩個函數(shù)的String成員函數(shù)都會涉及到該類問題惕稻,我們比如字符串截取函數(shù)Mid竖共,Left,Right等俺祠,都會返回臨時的String對象公给,使用右值引用后,臨時對象內(nèi)存申請釋放存在浪費的問題也就得到解決蜘渣。C++11中有很多好的特性淌铐,但是使用起來也會有點門檻,還是推薦在項目實踐的過程中蔫缸,慢慢學習和理解這些特性腿准。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市捂龄,隨后出現(xiàn)的幾起案子释涛,更是在濱河造成了極大的恐慌加叁,老刑警劉巖倦沧,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唇撬,死亡現(xiàn)場離奇詭異,居然都是意外死亡展融,警方通過查閱死者的電腦和手機窖认,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來告希,“玉大人扑浸,你說我怎么就攤上這事⊙嗯迹” “怎么了喝噪?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長指么。 經(jīng)常有香客問我酝惧,道長,這世上最難降的妖魔是什么伯诬? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任晚唇,我火速辦了婚禮,結果婚禮上盗似,老公的妹妹穿的比我還像新娘哩陕。我一直安慰自己,他們只是感情好赫舒,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布悍及。 她就那樣靜靜地躺著,像睡著了一般接癌。 火紅的嫁衣襯著肌膚如雪并鸵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天扔涧,我揣著相機與錄音园担,去河邊找鬼。 笑死枯夜,一個胖子當著我的面吹牛弯汰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播湖雹,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼咏闪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了摔吏?” 一聲冷哼從身側響起鸽嫂,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤纵装,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后据某,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體橡娄,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年癣籽,在試婚紗的時候發(fā)現(xiàn)自己被綠了挽唉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡筷狼,死狀恐怖瓶籽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情埂材,我是刑警寧澤塑顺,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站俏险,受9級特大地震影響严拒,放射性物質發(fā)生泄漏。R本人自食惡果不足惜寡喝,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一糙俗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧预鬓,春花似錦巧骚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至顶猜,卻和暖如春沧奴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背长窄。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工滔吠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留偷俭,地道東北人设哗。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像盅安,于是被迫代替她去往敵國和親嚣潜。 傳聞我的和親對象是個殘疾皇子冬骚,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • c++中引入了右值引用和移動語義只冻,可以避免無謂的復制庇麦,提高程序性能。有點難理解喜德,于是花時間整理一下自己的理解山橄。 左...
    StormZhu閱讀 113,593評論 42 191
  • C++右值引用 右值引用應該是C++11引入的一個非常重要的技術,因為它是移動語義(Move semantics)...
    小白將閱讀 2,181評論 2 13
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔住诸,今天18年5月份再次想寫文章驾胆,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,732評論 2 9
  • 我一生都在追隨 不停的不停的 后來 我死在了路上 他們的腳 踩在我的尸體上 不停的不停的
    赑十三閱讀 176評論 0 4
  • 大三的自己涣澡,覺得對未來好迷茫好迷茫贱呐。不知道未來的路怎么樣,也不清楚該何去何從入桂,感覺好煩好煩啊……
    何小左閱讀 242評論 0 0