c++11新特性の右値

7086 ワード

「C++11標準新特性の右」
右の値とは何ですか.何に使いますか.T&&は何ですか.
第1節はあなたに分を分けて右の値をマスターすることを教えます
今日はc++11で導入された新しい概念-右値についてお話しします.C++98の年代には、実は私たちもこの概念を持っていましたが、当時は抽象にこのような言葉を明確に出していませんでした.では、右の値とは何かを聞くかもしれません.私が出すことができる最も簡単な答えは「等号の左側に現れてはいけない値はすべて右値です!」どのようにこの言叶を理解して、例えば
/*1-1*/

//! b   c    int  
int a = b + c;  

このうちb + cは右の値です.b + c= ...のようなコードは書けないからです.この説明は簡単ですか.中毒にならないと思ったら、もう少し複雑な例を挙げましょう.
/*1-2*/

class A{};

A func(){
    return A();
}

//!    a   ,           
//!  func          ,       func()=...   
//!     func()    
A a = func();


私たちが理解できる定義を与えた後、C++の右の値の定義を見てみましょう.C++の右の値は2つの概念からなり,1つはxvalueと呼ばれ,もう1つはprvalueと呼ばれている.xvalue(eXpiring Value)とは、通常、ライフサイクルがもうすぐ終わる値を指します.例えば、1-2のfunc()の戻り値という一時的なオブジェクトです.prvalueは私たちの最初の例(b+c)です.中国語では純右値(Pure Rvalue)と呼ばれています.
第2節の右の値は何に使いますか.
右の値の設計は私から見れば、主な目的は2つの第1があり、moveの意味を実現するために使用される第2の目的は完璧な転送に使用される.よし、人の話をしよう!
moveの意味を説明する前に、非常に簡単な例を見て、実行できるコードを見てみましょう~.
あなたが私のリズムに従うことができることを確保するために、まずあなたが手に入れるテキストエディタを用意して、私はVIMを使っています.emacを使ったり、他の任意のバーを使ったりすることができます.コードを叩くことができれば、どんなツールでもいいです.次に、パソコンにgccがインストールされているか、clangがインストールされていることを確認してください.本当のc++11標準のコードを書くからです.高度に統合された開発IDEを使用することをお勧めしません.これらのツールはプロジェクトを開発するときに効率を高めることができるかもしれませんが、言語を学ぶ段階では、多くの仕事をしているので、最も基本的な知識を学ぶのに不利です.
さあ、袖を引っ張ってコードを叩く準備を始めましょう.まずフォルダを作成して、私たちのテストコードを保存するためにProjectと言います.プロジェクトの下に2つのファイルbuild.shRValue.cppを新規作成する.build.shの内容は以下の通りです
#!/bin/sh

g++ ./RValue.cpp -o RValue && {
    ./RValue
}

シナリオではまず、RValueをg++でコンパイルします.cpp(後で書きます)は、-oでコンパイルされた実行可能ファイルに良い名前を付け、指定しなければデフォルトでa.outと呼ばれます.コンパイルに成功したら、スクリプトにRValueを自動的に実行させます.後でRValueを書き終わります.cppを実行すると、./build.shを実行して実行結果をコンパイルして表示できます.簡単ですよ.ところで、実行する前に./build.shファイルのプロパティを変更して、実行可能なプロパティを追加することを忘れないでください.そうしないと、システムは実行できることを知りません.
chmod +x ./build.sh

ついに私たちの主役を書くべきで、本当に容易ではありません.少し休憩して、コーヒーを飲んで10分休んで、コードをかき始めますRValue.cppの内容は以下の通りです.
#include 
using namespace std;

class MyString{
public:
    //!     ,        _ptr    
    MyString()
        :_ptr(new char[10]){
    }

    //!       
    MyString(const MyString &s)
        :_ptr(s._ptr){
    }

    //!      ,        _ptr   
    ~MyString(){
        delete[] _ptr;
    }
private:
    char *_ptr;
};

int main(){
    MyString s1;
    MyString s2(s1);
    return 0;
}

コードが書き終わったら、スクリプトを試してみましょう./build.sh、運行が順調かどうか見てみましょう.Ohh, My God!! プログラムがクラッシュしました.私たちはこのようなヒントを得ました.
    error for object 0x7fc533c02870: pointer being freed 
    was not allocated

慌てないで~なぜcrashになったのか見てみましょう.MyStringにはchar *_ptrのメンバー変数があり、MyStringオブジェクトが破棄されると同時にdelete[] _ptrが呼び出されてメモリが解放されます.しかし、MyString s2(s1)を実行すると、MyStringのコピーコンストラクタが呼び出され、s 1の_ptrはs 2に与える_ptr、つまりs 1の_ptr実はs 2の_ptrは同じメモリ領域を指す.s 2ライフサイクル終了時に一度自分を解放する_ptr,s 1も同様に自分を解放する_ptr,2つのオブジェクトの_ptrはまた同じメモリ空間であり、これはこの空間が2回解放され、2回目の解放がcrashをトリガーすることを意味する.
MyStringのコピーコンストラクション関数を削除してコンパイル実行すると、このエラーがまだ発生しています.これは、デフォルトでは、コンパイラがコピーコンストラクタを生成し、デフォルトのコピーコンストラクタはビット単位でコピーされ、実装と同じです.これが一般的に言われている浅いコピーです.
crashの原因を知った後、私たちはこのバグを修復し、MyStringのコピー構造関数を修正しようとしました.
#include 
using namespace std;

class MyString{
public:
    MyString()
        :_ptr(new char[10]){
    }

    //!         
    //!  _ptr      ,   s._ptr            
    MyString(const MyString &s)
        :_ptr(new char[10]){
        memcpy(_ptr, s._ptr, 10);
    }

    ~MyString(){
        delete[] _ptr;
        _ptr = nullptr;
    }
private:
    char *_ptr;
};

int main(){
    MyString s1;
    MyString s2(s1);
    return 0;
}


新しいバージョンでは、コード注釈に加えられたように、浅いコピーの方式を深いコピーに変更し、コンパイルして実行し、すべてがそんなに穏やかです~.ニュースがなければ最高のニュースではないでしょうか.
次に、各関数が何回実行されているかを見てみましょう.コードにカウンタを追加して、これらのデータを統計するのに役立ちます.
#include 
using namespace std;

class MyString{
public:
    MyString()
        :_ptr(new char[10]){
            cout<<__func__ mystring="" :_ptr="" char="" memcpy="" s._ptr="" cout="" delete="" _ptr="" private:="" static="" int="" n_c="" n_cp="" n_d="" mystring::n_c="0;" mystring::n_cp="0;" mystring::n_d="0;" temp_string="" return="" main="" a="temp_string();"/>

上のコードでは、n_を使用しています.c構造関数の呼び出し回数を統計し、n_cp統計コピー構造の回数,n_d解析関数の呼び出し回数を統計する.
新しいコードロジックはもっと簡単で、temp_を呼び出すことによってstring()はMyStringオブジェクトを作成しました
コンパイラの最適化を阻止し、最も原始的なc++の動作を容易に表示するために、コンパイルスクリプトbuild.shを修正し、-fno-elide-constructorsのコンパイルオプションを追加する必要があります.
#!/bin/sh

g++ ./RValue.cpp -fno-elide-constructors -o RValue && {
./RValue
}

コード出力結果:出力と照らし合わせて、なぜか考えてみましょう.
MyString: 1
MyString: 1
~MyString: 1
MyString: 2
~MyString: 2
~MyString: 3 

プロセス全体を3つのステップに分割します.
  • 最初のステップtemp_string関数ではコンストラクション関数を使用して、MyStringオブジェクト
  • を構築します.
  • 第2のステップは、このオブジェクトをコピー構造により一時オブジェクトを戻り値
  • として構築する.
  • 第3のステップは、一時オブジェクトをコピー構造関数によってオブジェクトaを構築する.

  • 全過程で3つのオブジェクトに関連するため,3回の構造3次構造関数の呼び出しを見た.メモリが小さいオブジェクトでは、このような動作は無理ですが、比較的時間がかかる構造では、非常に効率的な問題をもたらします.
    では、一時オブジェクトを生成する際に、時間のかかるメモリ割り当てやコピー操作を行わない方法はありませんか.一時オブジェクトの出現自体がプログラマーに透明であり、性能の問題をもたらすだけでなく、プログラマーにとって、他に直感的な感知はありません.一時的なオブジェクトによって構築されたときに呼び出されるコンストラクション関数を定義することはできませんが、このコンストラクション関数での時間のかかる操作は、浅いコピーという迅速な方法に変更することができます.
    この問題を投げ出した後、賢いあなたはきっと考えたに違いありません.前に右の値の問題を議論したことがあります.この一時的なオブジェクトは右の値ではありませんか.では、右の値参照構造関数を決めることができますか.左参照と区別するために、c++11ではT&&&を用いて右参照を表す.だから、さっきの例では、右の引用構造関数を楽しく追加することができます.
    #include 
    using namespace std;
    
    class MyString{
    public:
        MyString()
            :_ptr(new char[10]){
                cout<<__func__> "< "< "<

    様々な構造関数呼び出しをより明確に見るために,logコンテンツを区別した.実行結果は次のとおりです.
    MyString: 1
    MyString: 1
    ~MyString: 1
    MyString: 2
    ~MyString: 2
    ~MyString: 3  
    

    皆さんは、実行結果に基づいて呼び出しプロセスを分析しましょう.前の部分の内容を真剣に読んだら、この結果には意外に感じないはずです.
     MyString(MyString&& s)
            :_ptr(s._ptr){
                cout<<__func__> "<

    右の構造関数では、sのリソースを_に移動したように、浅いコピーを行いました.ptrなのでmoveの概念があります.さらに、この関数に一時オブジェクト、すなわち真の右値を呼び出すだけでなく、c++11標準ライブラリにはstd::moveがmoveの意味を提供する.例を見れば一目瞭然だ.
    int main(){
        /* MyString a = temp_string(); */
        MyString a;
        MyString b(std::move(a));
        return 0;
    }
    

    main関数を少し修正すると、a自体が左の値であることがわかりますが、人為的に構造を移動することでbを構築すると思います.これにより、10バイトのメモリスペースを追加する必要がなくなり、std::move(a)を借りて右の値を返し、bオブジェクトの右の値参照構造をトリガーします.実行結果は次のとおりです.
    MyString: 1
    MyString: 1
    ~MyString: 1
    ~MyString: 2
    

    ここまで言うと,基本的に右引用とmove意味の関係が明らかになる.次の編では、C++の興奮する完璧な転送について話しましょう.この話題を見逃さないでください.失望しません.