ホームページ > バックエンド開発 > C#.Net チュートリアル > C++11 における右辺値参照、転送セマンティクス、完全転送の簡単な分析

C++11 における右辺値参照、転送セマンティクス、完全転送の簡単な分析

高洛峰
リリース: 2017-01-23 14:11:24
オリジナル
1508 人が閲覧しました

1. 左辺値と右辺値:

C++ には左辺値と右辺値の標準定義はありませんが、広く認識されている格言があります。アドレスを取得でき、名前を持ち、非一時的なものは次のとおりです。左辺値; 名前のないアドレスを取ることができないものは、右辺値です

即時値、関数によって返される値などはすべて非匿名オブジェクト (変数を含む) であることがわかります。関数、const オブジェクトなどによって返されるものはすべて左辺値です

基本的に、プログラマは、その右辺値 (即値を含む) がバックグラウンドで制御されることを確認することしかできません。このコード行で有効なのは右辺値 (即値を含む) であり、ユーザーが作成したものはスコープ ルールを通じて定期的にその存続を知ることができます (関数および const オブジェクトによって返されるローカル変数への参照を含みます)。例:

int& foo(){int tmp; return tmp;}
 
int fooo(){int tmp; return tmp;}
 
int a=10;
 
const int b;
 
int& temp=foo();//虽然合法,但temp引用了一个已经不存在的对象
 
int tempp=fooo();
ログイン後にコピー

上記のコードでは、a、temp、および foo() はすべて非定数左辺値であり、b は定数左辺値、fooo() は非定数右辺値、10 は a定数右辺値の場合、特に注意すべきことが 1 つあります。返される参照は左辺値 (アドレスを取得できる) であるということです。

一般的に言えば、コンパイラは右辺値の変更を許可しません(右辺値の有効期間はプログラマによって制御されないため、右辺値が変更されても使用できない可能性があります)、特に組み込み型オブジェクトの場合ただし、C++ では、右辺値オブジェクトを使用してメンバー関数を呼び出すことは許可されていますが、同じ理由で、そうしないことをお勧めします。

右辺値参照の表現方法は

Datatype&& variable
ログイン後にコピー

です。

Right Value 参照は C++11 の新機能であるため、C++98 の参照は、rvalue にバインドされた後、rvalue の有効期間をバインドするために使用されます。バインドされている右辺値参照の有効期間とは異なり、右辺値参照の存在は左辺値参照を置き換えるのではなく、右辺値の構築 (特に一時オブジェクト) を最大限に利用して、実現するオブジェクトの構築・破棄操作の削減 例えば以下の関数において、効率化を目的とします。

コンパイラがRVO(戻り値最適化)最適化を行わないことを前提として、以下の操作を行います。 :

(Demo是一个类)
Demo foo(){
  Demo tmp;
  return tmp;
}
ログイン後にコピー

は、コンストラクター (tmp) を 3 回呼び出します (x、一時オブジェクト)。これに対応して、オブジェクトが破棄されるとき、および右辺値参照メソッドが使用されている場合、デストラクターは 3 回呼び出されます:

Demo x=foo();
ログイン後にコピー

その場合、x を構築する必要はありません。元々破棄される予定だった一時オブジェクトも、x のバインドにより、その寿命が x と同じに延長されます (x が一時的なオブジェクトを与えることが理解できます)これには効率の向上が必要です (tmp が 4 バイトのスペースを占有する必要がありますが、これは簡単です)

右辺値参照と左辺値参照のバインディング ルール:

非定数左辺値参照は非定数左辺値にのみバインドできます。 非定数右辺値参照は非定数右辺値にのみバインドできます (vs2013 は定数右辺値にもバインドできます);定数右辺値参照はバインドのみ可能です定数と非定数右辺値へ (非定数右辺値参照は完全なセマンティクスのためにのみ存在し、定数左辺値参照はその役割を達成できます) 定数左辺値参照も右辺値にバインドできますが、明らかに右辺値の値です。右辺値参照は変更できます。したがって、右辺値参照はバインドされた右辺値を変更するため、バインドされた右辺値を const にすることはできません。

注: 右辺値参照は左辺値です!

3。導入される右辺値参照の目的の 1 つは、転送セマンティクスを実装することです。転送セマンティクスは、リソース (ヒープ、システム オブジェクトなど) の所有権を 1 つのオブジェクト (通常は匿名の一時オブジェクト) から別のオブジェクトに転送することで、オブジェクトの構築と破棄の操作、およびプログラムの効率の向上 (これは 2 の例で説明しました) 転送セマンティクスからは、実際には転送セマンティクスが新しい概念ではないことがわかります。実際に C++98/03 の言語やライブラリで使用されており、コンテキストでのコピー コンストラクターの省略、スマート ポインターのコピー (auto_ptr "copy")、リンク リストのスプライシング (list::splice)、統合された構文とセマンティックのサポートがまだないというだけです

通常の関数と演算子は右辺値参照を使用して転送セマンティクスを実装することもできますが (2 の例など)、転送セマンティクス通常、転送コンストラクターと転送代入演算子によって実装されます。転送コンストラクターのプロトタイプは Classname(Typename&&) ですが、コピー コンストラクターのプロトタイプはコンパイラーによって自動的に生成されないため、必要になります。転送コンストラクターを定義するだけではコンパイルに影響しません。渡されたパラメーターが左辺値の場合は、コピー コンストラクターが呼び出されます。例:

りー

从以上代码可以看出,拷贝构造函数在堆中重新开辟了一个大小为10000的int型数组,然后每个元素分别拷贝,而转移构造函数则是直接接管参数的指针所指向的资源,效率搞下立判!需要注意的是转移构造函数实参必须是右值,一般是临时对象,如函数的返回值等,对于此类临时对象一般在当行代码之后就被销毁,而采用转移构造函数可以延长其生命期,可谓是物尽其用,同时有避免了重新开辟数组.对于上述代码中的转移构造函数,有必要详细分析一下:

Demo(Demo&& lre):arr(lre.arr),size(lre.size)({lre.arr=NULL;}
ログイン後にコピー

lre是一个右值引用,通过它间接访问实参(临时对象)的资源来完成资源转移,lre绑定的对象(必须)是右值,但lre本身是左值;

因为lre是函数的局部对象,”lre.arr=NULL"必不可少,否则函数结尾调用析构函数销毁lre时仍然会将资源释放,转移的资源还是被系统收回.

4. move()函数

3中的例子并非万能,Demo(Demo&& lre)的实参必须是右值,有时候一个左值即将到达生存期,但是仍然想要使用转移语义接管它的资源,这时就需要move函数.

std::move函数定义在标准库中,它的作用是将左值强行转化为右值使用,从实现上讲,std:move等同于static_cast(lvalue) ,由此看出,被转化的左值本身的生存期和左值属性并没有被改变,这类似于const_cast函数.因此被move的实参应该是即将到达生存期的左值,否则的话可能起到反面效果.

5. 完美转发(perfect forwarding)

完美转发指的是将一组实参"完美"地传递给形参,完美指的是参数的const属性与左右值属性不变,例如在进行函数包装的时候,func函数存在下列重载:

void func(const int);
void func(int);
void func(int&&);
ログイン後にコピー

如果要将它们包装到一个函数cover内,以实现:

void cover(typename para){
  func(para);
}
ログイン後にコピー

使得针对不同实参能在cover内调用相应类型的函数,似乎只能通过对cover进行函数重载,这使代码变得冗繁,另一种方法就是使用函数模板,但在C++ 11之前,实现该功能的函数模板只能采用值传递,如下:

template<typename T>
void cover(T para){
  ...
  func(para);
  ...
}
ログイン後にコピー

但如果传递的是一个相当大的对象,又会造成效率问题,要通过引用传递实现形参与实参的完美匹配(包裹const属性与左右值属性的完美匹配),就要使用C++ 11 新引入的引用折叠规则:

函数形参 T的类型 推导后的函数形参

T& A& A&
T& A&& A&
T&& A& A&
T&& A&& A&&

因此,对于前例的函数包装要求,采用以下模板就可以解决:

template<typename T>
void cover(T&& para){
  ...
  func(static_cast<T &&>(para));
  ...
}
ログイン後にコピー

如果传入的是左值引用,转发函数将被实例化为:

void func(T& && para){
 
  func(static_cast<T& &&>(para));
 
}
ログイン後にコピー

应用引用折叠,就为:

void func(T& para){
 
  func(static_cast<T&>(para));
 
}
ログイン後にコピー

如果传入的是右值引用,转发函数将被实例化为:

void func(T&& &¶){
 
   func(static_cast<T&& &&>(para));
}
ログイン後にコピー

应用引用折叠,就是:

void func(T&& para){
 
  func(static_cast<T&&>(para));
 
}
ログイン後にコピー

对于以上的static_cast ,实际上只在para被推导为右值引用的时候才发挥作用,由于para是左值(右值引用是左值),因此需要将它转为右值后再传入func内,C++ 11在定义了一个std::forward函数来实现以上行为,

所以最终版本

template<typename T>
 
void cover(T&& para){
 
  func(forward(forward<T>(para)));
 
}
ログイン後にコピー

std::forward的实现与static_cast(para)稍有不同

std::forward函数的用法为forward(para) , 若T为左值引用,para将被转换为T类型的左值,否则para将被转换为T类型右值

总结

以上就是关于C++11中右值引用、转移语义和完美转发的全部内容,这篇文章介绍的很详细,希望对大家的学习工作能有所帮助。

更多浅析C++11中的右值引用、转移语义和完美转发相关文章请关注PHP中文网!

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート