C / C ++プログラミング:コンストラクターの移動

:私たちは知っていることを右辺値参照は主に移動セマンティクスと完璧な転送を実装するために使用されていますでは、何がセマンティクスを動かすのでしょうか?それはどのように達成されますか?

導入する

C ++ 11より前は、同じ種類の新しいオブジェクトを他のオブジェクトで初期化する場合、クラスで使用できるのはコピー(コピー)コンストラクターのみです。

コピーコンストラクターの実装原理は非常に単純です。つまり、新しいオブジェクトの他のオブジェクトと同じデータをコピーします(クラスにポインター型のメンバー変数がある場合、コピーコンストラクターはポインターメンバーをコピーする必要があることに注意してください)。ディープコピーモードで。さらに、静的メンバー変数の処理にも注意してください)

例えば:

#include <iostream>
using namespace std;
class demo{
    
    
public:
   demo():num(new int(0)){
    
    
      cout<<"construct!"<<endl;
   }
   //拷贝构造函数
   demo(const demo &d):num(new int(*d.num)){
    
    
      cout<<"copy construct!"<<endl;
   }
   ~demo(){
    
    
      cout<<"class destruct!"<<endl;
   }
private:
   int *num;
};
demo get_demo(){
    
    
    return demo();
}
int main(){
    
    
    demo a = get_demo();
    return 0;
}

上に示したように、デモクラスのコピーコンストラクターをカスタマイズしました。この関数がd.numポインタメンバーをコピーするときは、ディープコピー方式を採用する必要があります。つまり、ポインタメンバー自体をコピーするときに、ポインタが指すメモリリソースもコピーします。それ以外の場合、複数のオブジェクトのポインターメンバーが同じヒープスペースを指すと、これらのオブジェクトが破棄されたときにスペースが複数回解放されます。これは許可されていません。

ご覧のとおり、プログラムは、main()メイン関数でオブジェクトを初期化するために使用されるデモオブジェクトを返すことができるget_demo()関数を定義します。初期化プロセス全体には、次の段階が含まれます。

  • get_demo()関数内でdemo()ステートメントを実行します。つまり、デモクラスのデフォルトコンストラクターを呼び出して、匿名オブジェクトを生成します。
  • return demo()ステートメントを実行すると、コピーコンストラクターが呼び出され、以前に生成された匿名オブジェクトのコピーが作成され、それが--get_demo()関数の戻り値として使用されます(関数本体が実行される前に、匿名オブジェクトは次のようになります。破壊され破壊された);
  • = get_demo()ステートメントを実行し、コピーコンストラクターを再度呼び出して、前にコピーした一時オブジェクトをにコピーします(このコード行が実行された後、get_demo()関数によって返されるオブジェクトは破棄されます)。
  • プログラムの実行が終了する前に、デモクラスのデストラクタが呼び出されてaが破棄されます。

現在、ほとんどのコンパイラはプログラムで発生するコピー操作を最適化しているため、このプログラムをVS 2017、コードブロック、およびその他のコンパイラで実行すると、最適化された出力結果が表示されることがよくあります。

construct!
class destruct!

また、同じプログラムで、Linuxでg ++ demo.cpp -fno-elide-constructorsコマンド(demo.cppはプログラムファイルの名前)を実行すると、完全な出力が表示されます。

construct!                <-- 执行 demo()
copy construct!       <-- 执行 return demo()
class destruct!         <-- 销毁 demo() 产生的匿名对象
copy construct!       <-- 执行 a = get_demo()
class destruct!         <-- 销毁 get_demo() 返回的临时对象
class destruct!         <-- 销毁 a

上に示したように、コピーコンストラクターを使用してオブジェクトを初期化すると、最下層は実際には2つのコピー(およびディープコピー)操作を実行します。もちろん、少量のヒープスペースにのみ適用される一時オブジェクトの場合、ディープコピーの実行効率は許容範囲内ですが、一時オブジェクトのポインタメンバーが大量のヒープスペースに適用される場合は、2回のディープコピー操作が行われます。オブジェクトの有効性の初期化に必然的に影響します

実際、この問題は、C ++ 98/03標準で記述されたC ++プログラムでも解決されていません。一時変数の生成、破棄、およびコピー操作は本質的に非常にあいまいであり(コンパイラーはこれらのプロセスに特別な最適化を行っています)、プログラムの正確性に影響を与えないため、プログラマーの視野に入ることはめったにありません。

では、ポインタ変数を含むクラスのメンバー関数が他のオブジェクトを使用して同様のオブジェクトを初期化する場合、ディープコピーによって引き起こされる効率の問題をどのように回避できますか?C ++標準では、ソリューションが導入されています。この標準では、移動セマンティクスを実現するために使用できる右辺値参照の構文が導入されています。

モバイルセマンティクスの実装

いわゆる移動セマンティクスは、ディープコピーの代わりに移動することによるポインタメンバーを含むクラスオブジェクトの初期化を指します簡単に理解すると、移動セマンティクスは、他のオブジェクト(通常は一時オブジェクト)が所有するリソースを「自分で使用するために移動する」ことです。

前のプログラムのデモクラスを例として取り上げます。このクラスのメンバーには、整数ポインターメンバーが含まれています。このメンバーは、デフォルトで整数変数を保持するヒープスペースを指します。get_demo()関数によって返された一時オブジェクトを使用してを初期化する場合、一時オブジェクトのnumポインターをa.numに直接浅くコピーしてから、一時オブジェクトのnumポインターのポインターを変更するだけです(通常はNULLを指す)ので、これでa.numの初期化が完了します。

実際、プログラムの実行中に生成された一時オブジェクトは、多くの場合、データの転送にのみ使用され(他の用途には使用されません)、まもなく破棄されます。したがって、一時オブジェクトを使用して新しいオブジェクトを初期化する場合、新しいコピーを作成しなくても、オブジェクトに含まれるポインタメンバーが指すメモリリソースを新しいオブジェクトに直接移動できます。これにより、初期化の実行効率が大幅に向上します。

たとえば、次のプログラムはデモクラスを変更します。

#include <iostream>
using namespace std;
class demo{
    
    
public:
    demo():num(new int(0)){
    
    
        cout<<"construct!"<<endl;
    }
    demo(const demo &d):num(new int(*d.num)){
    
    
        cout<<"copy construct!"<<endl;
    }
    //添加移动构造函数
    demo(demo &&d):num(d.num){
    
    
        d.num = NULL;
        cout<<"move construct!"<<endl;
    }
    ~demo(){
    
    
        cout<<"class destruct!"<<endl;
    }
private:
    int *num;
};
demo get_demo(){
    
    
    return demo();
}
int main(){
    
    
    demo a = get_demo();
    return 0;
}

ご覧のとおり、前のデモクラスに基づいて、コンストラクターを手動で追加しました。他のコンストラクターとは異なり、このコンストラクターは、移動コンストラクターとも呼ばれる右辺値参照の形式でパラメーターを使用しますまた、このコンストラクターでは、numポインター変数がシャローコピーコピーメソッドを採用すると同時に、d.numが関数内でリセットされるため、「同じスペースブロックが複数回解放される」という発生を効果的に回避できます。

g ++ demo.cpp -o demo.exe -std = c ++ 0x -fno-elide-constructorsコマンドを使用して、Linuxシステムでこのプログラムを実行すると、出力結果は次のようになります。

construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!

実行結果から、デモクラスにmoveコンストラクターを追加した後、一時オブジェクトでオブジェクトを初期化する過程で生成された2つのコピー操作がすべてmoveコンストラクターに転送されて完了することを知るのは難しいことではありません。

非const右辺値参照は右辺値のみを操作でき、プログラム実行の結果で生成された一時オブジェクト(関数の戻り値、ラムダ式など)には名前もストレージアドレスへのアクセスもないため、これらはに属します。右辺値。クラスにコピーコンストラクターと移動コンストラクターの両方が含まれている場合、一時オブジェクトを使用して現在のクラスのオブジェクトを初期化すると、コンパイラーは最初に移動コンストラクターを呼び出してこの操作を完了します。クラスに適切なmoveコンストラクターがない場合にのみ、コンパイラーが2番目になり、copyコンストラクターを呼び出します。

実際の開発では、通常、クラス内の移動コンストラクターをカスタマイズするときに、適切なコピーコンストラクターがカスタマイズされるため、ユーザーがクラスオブジェクトを右辺値で初期化すると、移動コンストラクターが呼び出されます。 rvalue)はクラスオブジェクトを初期化し、コピーコンストラクタは呼び出されます

質問:左辺値を使用して同様のオブジェクトを初期化するが、moveコンストラクターを呼び出して完了する場合、それを実現する方法はありますか?

デフォルトでは、同様のオブジェクトの左辺値の初期化は、コピーコンストラクターを介してのみ実行できます。移動コンストラクターを呼び出す場合は、初期化に右辺値を使用する必要があります。C ++ 11標準では、ユーザーが左辺値を使用してmoveコンストラクターを介して同様のオブジェクトを初期化するニーズを満たすためにstd::move()、左辺値を対応する右辺値に強制変換できる関数が導入されています。これにより、moveコンストラクターは次のことができます。利用される

おすすめ

転載: blog.csdn.net/zhizhengguan/article/details/115014024