Cpp 右辺値参照 新しいページはコチラ

提供: yonewiki
移動: 案内, 検索
 
1行: 1行:
 +
[[C PlusPlus#C++からの技術|C++]]に戻る
 +
 +
 
<table class="mbox-small" style="border:1px solid #aaa; background-color:#f9f9f9; width:22em;" id="RealTitleBanner">
 
<table class="mbox-small" style="border:1px solid #aaa; background-color:#f9f9f9; width:22em;" id="RealTitleBanner">
 
<tr>
 
<tr>
 
<td style="width:1px;"></td>
 
<td style="width:1px;"></td>
<td class="mbox-text plainlist" style="">本来の表記は「<b><span id="RealTitle" style="font-size:large;">C++ 右辺値参照</span></b>」です。この記事に付けられた題名は{{記事名の制約}}から不正確なものとなっています。</td>
+
<td class="mbox-text plainlist" style="">本来の表記は「<b><span id="RealTitle" style="font-size:large;">C++(Cpp) 右辺値参照</span></b>」です。この記事に付けられた題名は{{記事名の制約}}から不正確なものとなっています。</td>
 
</tr>
 
</tr>
 
</table>
 
</table>
8行: 11行:
 
<br />
 
<br />
 
== '''右辺値参照''' ==
 
== '''右辺値参照''' ==
 +
 
[[Cpp 参照]]の特殊なパターンとして右辺値参照があります。あまり知られていないという点で特殊と記述しましたが、知っているひとからすれば、ごく自然なC++の言語仕様の一部に過ぎないです。
 
[[Cpp 参照]]の特殊なパターンとして右辺値参照があります。あまり知られていないという点で特殊と記述しましたが、知っているひとからすれば、ごく自然なC++の言語仕様の一部に過ぎないです。
  
  
右辺値というとa = b;のように書くとbが方程式の右辺値です。このようなものの参照?って言われてもピンとこないかもしれませんが、よく右辺値にくるものの参照という意味で、オブジェクトをもたいない値の参照というような技術になります。オブジェクトをもたない値というのは具体的には、以下のようなものの右辺にくるものです。
+
右辺値というとa = b;のように書くとbが方程式の右辺値です。このようなものの参照?って言われてもピンとこないかもしれませんが、よく右辺値にくるものの参照という意味で、オブジェクトをもたない値の参照というような技術になります。オブジェクトをもたない値というのは具体的には、以下のようなものの右辺にくるものです。
  
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
34行: 38行:
  
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
int = nSize
+
int nSize;
 
int&& rrefnSize = nSize; //これは駄目。
 
int&& rrefnSize = nSize; //これは駄目。
 
</syntaxhighlight>
 
</syntaxhighlight>
46行: 50行:
 
以下は[[C キャスト | キャスト]]による明示的な右辺値参照代入の指定です。
 
以下は[[C キャスト | キャスト]]による明示的な右辺値参照代入の指定です。
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
int = nSize
+
int nSize;
 
int&& rrefnSize = static_cast<int&&>(nSize); //これはOK。
 
int&& rrefnSize = static_cast<int&&>(nSize); //これはOK。
 
</syntaxhighlight>
 
</syntaxhighlight>
52行: 56行:
  
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
int = nSize
+
int nSize;
 
int&& rrefnSize = std::move(nSize); //キャストと同じで、OK。
 
int&& rrefnSize = std::move(nSize); //キャストと同じで、OK。
 
</syntaxhighlight>
 
</syntaxhighlight>
86行: 90行:
 
   printf("rrefnCReturn2      Addres->%x, rrefnCReturn2      Value->%d\n",&rrefnCReturn2,rrefnCReturn2);
 
   printf("rrefnCReturn2      Addres->%x, rrefnCReturn2      Value->%d\n",&rrefnCReturn2,rrefnCReturn2);
  
   int&& rrefnStaticCReturn = CTest::getIntegerNum();//クラス関数(static)の右辺値参照代入
+
   int&& rrefnStaticCReturn = CTest::getIntegerNumStatic();//クラス関数(static)の右辺値参照代入
 
   printf("rrefnStaticCReturn  Addres->%x, rrefnStaticCReturn  Value->%d\n",&rrefnStaticCReturn,rrefnStaticCReturn);
 
   printf("rrefnStaticCReturn  Addres->%x, rrefnStaticCReturn  Value->%d\n",&rrefnStaticCReturn,rrefnStaticCReturn);
  
   int&& rrefnStaticCReturn2 = CTest::getIntegerNum();//クラス関数(static)の右辺値参照代入
+
   int&& rrefnStaticCReturn2 = CTest::getIntegerNumStatic();//クラス関数(static)の右辺値参照代入
 
   printf("rrefnStaticCReturn2 Addres->%x, rrefnStaticCReturn2 Value->%d\n",&rrefnStaticCReturn2,rrefnStaticCReturn2);
 
   printf("rrefnStaticCReturn2 Addres->%x, rrefnStaticCReturn2 Value->%d\n",&rrefnStaticCReturn2,rrefnStaticCReturn2);
 
   return 0;
 
   return 0;
107行: 111行:
  
 
このような右辺値戻り値を引数にとるような関数で、ポインタ変数のアドレスだけを移動させることによる、付け替え関数を作ることができます。言葉での説明ではイメージできないと思うので、実際にやってみた方がいいですね。
 
このような右辺値戻り値を引数にとるような関数で、ポインタ変数のアドレスだけを移動させることによる、付け替え関数を作ることができます。言葉での説明ではイメージできないと思うので、実際にやってみた方がいいですね。
 +
 +
Mainプログラム
 +
<syntaxhighlight lang="cpp" line start="1">
 +
#include "stdafx.h"
 +
#include "RightRefSample.h"
 +
 +
int _tmain(int argc, _TCHAR* argv[])
 +
{
 +
  printf("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★\n");
 +
  printf("★★開始\n");
 +
  printf("\n");
 +
  printf("★オリジナルのクラス生成 Inst001\n");
 +
  CRightRefSample* crightrefsampleInst001 = new CRightRefSample("001");
 +
 +
  printf("\n");
 +
  printf("★2個目のクラス生成 Inst001を初期値でコピーしたクラスInst002\n");
 +
  CRightRefSample* crightrefsampleInst002 = new CRightRefSample(*crightrefsampleInst001);
 +
 +
  printf("\n");
 +
  printf("★3個目のクラス生成Inst003\n");
 +
  CRightRefSample* crightrefsampleInst003 = new CRightRefSample("003");
 +
 
 +
  printf("\n");
 +
  printf("★4個目のクラス生成Inst004\n");
 +
  CRightRefSample* crightrefsampleInst004 = new CRightRefSample("004");
 +
 +
  printf("★状態表示\n");
 +
  printf("★m_pStrChar:001->%06x 002->%06x 003->%06x 004->%06x\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
  printf("★m_pStrChar:001->%6s 002->%6s 003->%6s 004->%6s\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
 +
 +
  printf("\n");
 +
  printf("★3個目に生成したInst003へInst001をコピー\n");
 +
  *crightrefsampleInst003 = *crightrefsampleInst001;
 +
 +
  printf("★状態表示\n");
 +
  printf("★m_pStrChar:001->%06x 002->%06x 003->%06x 004->%06x\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
  printf("★m_pStrChar:001->%6s 002->%6s 003->%6s 004->%6s\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
 +
 +
  printf("\n");
 +
  printf("★Inst001とInst003を交換\n");
 +
  CRightRefSample::swap(*crightrefsampleInst001,*crightrefsampleInst003);
 +
  printf("★状態表示\n");
 +
  printf("★m_pStrChar:001->%06x 002->%06x 003->%06x 004->%06x\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
  printf("★m_pStrChar:001->%6s 002->%6s 003->%6s 004->%6s\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
 +
  printf("\n");
 +
  printf("★4個目に生成したInst004へInst001をムーブ\n");
 +
  *crightrefsampleInst004 = std::move(*crightrefsampleInst001);
 +
  printf("★状態表示\n");
 +
  printf("★m_pStrChar:001->%06x 002->%06x 003->%06x 004->%06x\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
  printf("★m_pStrChar:001->%6s 002->%6s 003->%6s 004->%6s\n",
 +
    crightrefsampleInst001->getStrValueAddress(),
 +
    crightrefsampleInst002->getStrValueAddress(),
 +
    crightrefsampleInst003->getStrValueAddress(),
 +
    crightrefsampleInst004->getStrValueAddress());
 +
 +
  printf("\n");
 +
  printf("★生成したインスタンスの全削除手動\n");
 +
  printf("Inst001 Delete\n");
 +
  delete crightrefsampleInst001;
 +
 +
  printf("Inst002 Delete\n");
 +
  delete crightrefsampleInst002;
 +
 +
  printf("Inst003 Delete\n");
 +
  delete crightrefsampleInst003;
 +
 +
  printf("Inst004 Delete\n");
 +
  delete crightrefsampleInst004;
 +
  printf("★★終了\n");
 +
  printf("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★\n");
 +
  return 0;
 +
}
 +
</syntaxhighlight>
 +
RightRefSample.h
 +
<syntaxhighlight lang="cpp" line start="1">
 +
#pragma once
 +
#include <iostream>
 +
 +
using namespace std;
 +
 +
class CRightRefSample
 +
{
 +
private:
 +
  char* m_pcStrValue;
 +
public:
 +
  int Function(){return 1;};
 +
  CRightRefSample(void);
 +
  CRightRefSample(const char* pconstcDefaultValue);
 +
  CRightRefSample(const CRightRefSample& refcrightrefsampleInst );
 +
  CRightRefSample(CRightRefSample&& rrefcrightrefsampleInst );
 +
  CRightRefSample& operator= (const CRightRefSample& refcrightrefsampleInst);
 +
  CRightRefSample& operator= (CRightRefSample&& refcrightrefsampleInst);
 +
 +
  static void swap(CRightRefSample& refcrightrefsampleA,CRightRefSample& refcrightrefsampleB);
 +
  char* getStrValueAddress(){return m_pcStrValue;};
 +
 +
  ~CRightRefSample(void);
 +
};
 +
</syntaxhighlight>
 +
RightRefSample.cpp
 +
<syntaxhighlight lang="cpp" line start="1">
 +
#include "stdafx.h"
 +
#include "RightRefSample.h"
 +
 +
//コンストラクタ
 +
CRightRefSample::CRightRefSample(void)
 +
{
 +
  printf("%06x: CRightRefSample (void)Constructor        :%s\n", this, __FUNCTION__);
 +
  m_pcStrValue = new char[1024];
 +
  strcpy_s(m_pcStrValue, 1024, "初期値");
 +
 
 +
}
 +
 +
CRightRefSample::CRightRefSample(const char* pconstcDefaultValue){
 +
  printf("%06x: CRightRefSample (const char)Constructor  :%s\n", this, __FUNCTION__);
 +
 
 +
  m_pcStrValue = new char[strlen(pconstcDefaultValue) + 1];
 +
  strcpy_s(m_pcStrValue, strlen(pconstcDefaultValue) + 1, pconstcDefaultValue);
 +
}
 +
 +
//コピーコンストラクタ
 +
CRightRefSample::CRightRefSample(const CRightRefSample& refcrightrefsampleInst ){
 +
  printf("%06x: CRightRefSample (Ref)CopyConstructor    :%s\n", this, __FUNCTION__);
 +
  m_pcStrValue = new char[1024];
 +
  strcpy_s(m_pcStrValue, 1024, refcrightrefsampleInst.m_pcStrValue);
 +
}
 +
 +
//ムーブコンストラクタ
 +
CRightRefSample::CRightRefSample(CRightRefSample&& rrefcrightrefsampleInst ){
 +
 
 +
  printf("%06x: CRightRefSample (RightRef)MoveConstructor:%s\n", this, __FUNCTION__);
 +
  m_pcStrValue = rrefcrightrefsampleInst.m_pcStrValue;//新しいクラスにだけ
 +
  rrefcrightrefsampleInst.m_pcStrValue = nullptr;
 +
}
 +
//=代入演算子の後ろに参照引数でコピー処理
 +
CRightRefSample& CRightRefSample::operator= (const CRightRefSample& refcrightrefsampleInst){
 +
  delete[] m_pcStrValue;
 +
 +
  printf("%06x: CRightRefSample (Ref)operator=          :%s\n", this, __FUNCTION__);
 +
  m_pcStrValue = new char[strlen(refcrightrefsampleInst.m_pcStrValue) + 1];
 +
  strcpy_s(m_pcStrValue,strlen(refcrightrefsampleInst.m_pcStrValue) + 1,refcrightrefsampleInst.m_pcStrValue);
 +
 +
  return *this;
 +
}
 +
//=代入演算子の後ろに右辺値参照引数でムーブ処理
 +
CRightRefSample& CRightRefSample::operator= (CRightRefSample&& refcrightrefsampleInst){
 +
  delete[] m_pcStrValue;
 +
 +
  printf("%06x: CRightRefSample (RightRef)operator=      :%s\n", this, __FUNCTION__);
 +
  m_pcStrValue = refcrightrefsampleInst.m_pcStrValue;
 +
  refcrightrefsampleInst.m_pcStrValue = nullptr;
 +
 +
  return *this;
 +
}
 +
//クラス変数の入れ替えムーブ版
 +
void CRightRefSample::swap(CRightRefSample& refcrightrefsampleA,CRightRefSample& refcrightrefsampleB){
 +
  printf("%06x: CRightRefSample (Ref, Ref)swap          :%s\n", 0, __FUNCTION__);
 +
  CRightRefSample crightrefsampleTemp = std::move(refcrightrefsampleA);//ムーブコンストラクタ
 +
  refcrightrefsampleA = std::move(refcrightrefsampleB);//代入演算子ムーブ処理
 +
  refcrightrefsampleB = std::move(crightrefsampleTemp);//代入演算子ムーブ処理
 +
}
 +
 +
//デストラクタ
 +
CRightRefSample::~CRightRefSample(void)
 +
{
 +
  printf("%06x: CRightRefSample (void)Destructor        :addres->%06x name->%s\n", this, m_pcStrValue, __FUNCTION__);
 +
  delete[] m_pcStrValue;
 +
}
 +
 +
</syntaxhighlight>
 +
非常に長いサンプルになってしまいましたが、こんな感じでClassのメンバ変数を入れ替えるようなプログラム(staticで定義したメンバ関数swap 静的メンバ関数はオブジェクトからでなくても呼べる関数です。別で詳細は説明の予定。thisポインタが使えないクラスの中にあるだけの関数CRightSample::swap(…)のように呼び出すものです。)を作ってみました。算術演算子 = をオーバロード(関数として定義)していまして、(CRightRefSampleのインスタンス) = の後ろに(CRightRefSampleのインスタンス)が設定された場合の関数と(CRightRefSampleのインスタンス)が右辺値参照として設定されている場合の関数の2つをオーバロードしています。同じ代入演算子でも引数が右辺値参照のときはムーブ、引数が参照のときはコピーのように使い分けることができます。右辺値参照の変数は使わなくなる予定の変数ですので、引数で受け取った変数をコピーするのではなくムーブしてしまうようにして利用します。特にswapのような入れ替えではポインタの交換だけを行うことで入れ替えられますので、AとBの入れ替え処理では入れ替え用の一時的な変数に対してAをムーブし、ムーブしおわったAにBの値をムーブ、そして一時的に作った変数からBへムーブという具合に3回のムーブによりポインタの入れ替えができて内容を交換できます。今回はひとつのメンバ変数のみの入れ替えサンプルですが、クラスの中の要素が文字列のような配列であったり、構造体、クラスといったようなもっと複雑なデータを保持している場合にムーブすることでデータの受け渡しをすることができます。コピーを作らなくてもいいような処理にはムーブは資源の節約にもなるし、無駄な処理が無いため処理速度も改善されます。ムーブのような作業は別に右辺値参照を使うことなくできますので、必ずしも右辺値参照が必要なわけではないですが、参照を引数にした場合の処理と分けることができるのが特徴です。今回はstd::moveのような関数であえて右辺値として扱うことでムーブを実現していますが、右辺値参照を引数にしている部分に引数にスタティックなクラスメンバ関数や関数を定義してクラスのインスタンスを戻り値とするような関数を作った場合に純粋な右辺値が登場しますが、そのような場合でも同じようにムーブされて問題ない処理になるはずです。右辺値参照を引数にとるムーブコンストラクタや代入演算子のオーバロードによるムーブを活用できることを知っておくことで、よりよいクラスの使い方が可能となります。同じような関数が多くなりがちですが、このあたりはテンプレートの活用によりさらにコーディングの効率はあげられると思います。ここでは、あまりややこしくならないように最小限のクラス化と、最小限のテンプレートクラスの利用でメモを作成しています。
 +
 +
 +
ムーブコンストラクタはコピーコンストラクタの引数が通常の参照であったのに対して、元の値をとっておかないような使い方として、引数を右辺値参照型にしているようなコンストラクタになっています。
 +
 +
 +
このプログラムを理解するにはオブジェクト(インスタンス)、ポインタ、nullptr、文字列操作、参照、クラス(コンストラクタ デストラクタ 静的メンバ関数 演算子のオーバロード オーバロード インライン関数)を理解しておく必要があります。
 +
 +
 +
サンプルプログラムの出力結果は以下のとおりです。
 +
<syntaxhighlight lang="text">
 +
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
 +
★★開始
 +
 +
★オリジナルのクラス生成 Inst001
 +
338240: CRightRefSample (const char)Constructor  :CRightRefSample::CRightRefSample
 +
 +
★2個目のクラス生成 Inst001を初期値でコピーしたクラスInst002
 +
3382a0: CRightRefSample (Ref)CopyConstructor    :CRightRefSample::CRightRefSample
 +
 +
★3個目のクラス生成Inst003
 +
3382d0: CRightRefSample (const char)Constructor  :CRightRefSample::CRightRefSample
 +
 +
★4個目のクラス生成Inst004
 +
338330: CRightRefSample (const char)Constructor  :CRightRefSample::CRightRefSample
 +
★状態表示
 +
★m_pStrChar:001->338270 002->33dbd0 003->338300 004->338360
 +
★m_pStrChar:001->  001 002->  001 003->  003 004->  004
 +
 +
★3個目に生成したInst003へInst001をコピー
 +
3382d0: CRightRefSample (Ref)operator=          :CRightRefSample::operator =
 +
★状態表示
 +
★m_pStrChar:001->338270 002->33dbd0 003->338300 004->338360
 +
★m_pStrChar:001->  001 002->  001 003->  001 004->  004
 +
 +
★Inst001とInst003を交換
 +
000000: CRightRefSample (Ref, Ref)swap          :CRightRefSample::swap
 +
24f908: CRightRefSample (RightRef)MoveConstructor:CRightRefSample::CRightRefSample
 +
338240: CRightRefSample (RightRef)operator=      :CRightRefSample::operator =
 +
3382d0: CRightRefSample (RightRef)operator=      :CRightRefSample::operator =
 +
24f908: CRightRefSample (void)Destructor        :addres->000000 name->CRightRefSample::~CRightRefSample
 +
★状態表示
 +
★m_pStrChar:001->338300 002->33dbd0 003->338270 004->338360
 +
★m_pStrChar:001->  001 002->  001 003->  001 004->  004
 +
 +
★4個目に生成したInst004へInst001をムーブ
 +
338330: CRightRefSample (RightRef)operator=      :CRightRefSample::operator =
 +
★状態表示
 +
★m_pStrChar:001->000000 002->33dbd0 003->338270 004->338300
 +
★m_pStrChar:001->(null) 002->  001 003->  001 004->  001
 +
 +
★生成したインスタンスの全削除手動
 +
Inst001 Delete
 +
338240: CRightRefSample (void)Destructor        :addres->000000 name->CRightRefSample::~CRightRefSample
 +
Inst002 Delete
 +
3382a0: CRightRefSample (void)Destructor        :addres->33dbd0 name->CRightRefSample::~CRightRefSample
 +
Inst003 Delete
 +
3382d0: CRightRefSample (void)Destructor        :addres->338270 name->CRightRefSample::~CRightRefSample
 +
Inst004 Delete
 +
338330: CRightRefSample (void)Destructor        :addres->338300 name->CRightRefSample::~CRightRefSample
 +
 +
★★終了
 +
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
 +
</syntaxhighlight>
  
  
戻り値を利用するとは、どういうことなのかを示す具体的なサンプルは作成中です。右辺値が必要になるようなプログラムって難しいですから、ちょっと時間下さい。っていつまでw
+
[[C PlusPlus#C++からの技術|C++]]に戻る

2021年2月6日 (土) 00:00時点における最新版



個人用ツール
名前空間

変種
操作
案内
ツールボックス