HOWTO · C++

C++ でこのポインタを使用する

このチュートリアルでは、このポインターとその必要性、および C++ での使用法を簡単に紹介します。さらに、例を使用して、これと*this の違いについて説明します。

このチュートリアルでは、最初に this ポインタの概念を理解します。

次に、例を使用してその使用法を示します。最後に、this*this とどのように異なるかを見ていきます。

C++ で this ポインタを使用する

this ポインターは、非静的クラスメンバー関数スコープで、呼び出し元オブジェクトを参照または指す、暗黙的に使用可能なポインターです。定義上理解できなかった場合は、概念を理解するためにいくつかの基本事項を見てみましょう。

すべての非静的データメンバーには、オブジェクトごとに個別のコピーがあることがわかっています。ただし、メンバー関数コードはすべてのオブジェクト間で共有されます。

同じクラスのすべてのオブジェクトは、呼び出す必要があるときに、コードセグメントから同じ関数定義にアクセスします。コンパイラは、この特定の呼び出し元オブジェクトに固有の適切なデータメンバーにアクセスまたは更新するために、呼び出し元オブジェクト(現在メンバー関数を呼び出している)情報を知る必要があります。

では、関数の定義が同じであるとすると、コンパイラはどのようにして呼び出し元のオブジェクト情報を取得するのでしょうか。

簡単な答えは、プログラムのコンパイル中に、コンパイラーがメンバー関数に非表示の暗黙のポインターパラメーターを自動的に追加することです。この暗黙のポインタは、this ポインタとして知られています。

メンバー関数が特定のクラスオブジェクトを介して呼び出されるたびに、呼び出し元のオブジェクトは、this ポインターへの隠し引数として自動的に提供されます。これで、コンパイラはこのポインタを使用して、呼び出し元オブジェクトに固有の適切なデータメンバーにアクセスまたは変更できます。

ノート
this ポインタは、クラスの静的メンバー(関数およびデータメンバー)がすべてのオブジェクト間で共有されるため、必要ありません。

静的メンバーの更新またはアクセスは、どのオブジェクトにも固有ではありません。クラスのオブジェクトを作成せずに、これらのメンバーにアクセスすることもできます。

したがって、コンパイラはこれらのメンバーで this 演算子を使用しません。

コード例を調べて、コンパイラーが暗黙的に this ポインターを使用すると言っている理由と、それが便利な場合を理解しましょう。

#include <iostream>
#include <string>
using namespace std;

class Person {
 private:
  string Name;

 public:
  Person(string Name) {
    this->Name = Name;  // this->Name is the private member for this object
  }
  void PrintName() { cout << this->Name << endl; }
  void PrintName1() { cout << Name << endl; }
};
int main() {
  Person P("Alexa");
  P.PrintName();
  P.PrintName1();
  return 0;
}

上記のコード例では、プライベートデータメンバーName を出力するための 2つの異なるメンバー関数を持つ Person クラスを定義しています。main() 関数の最初のステートメントは、Person オブジェクトを生成し、コンストラクターへの引数として Alexa を渡します。

現在、コンストラクターの this->Name は、コンパイラーがローカルパラメーターName とプライベートデータメンバーName を区別するのに役立ちます。

main() の後続のコードは、P を介して printName() および printName1() を呼び出します(つまり、P が呼び出し元オブジェクトになります)。

出力:

Alexa
Alexa

両方の関数の出力は同じです。これは、コンパイラが PrintName1()this-> の前に Name を暗黙的に付けるためです。

C++ での this*this の違い

これまでのところ、this はオブジェクトへのポインタであるという概念は明らかです。タイプ Personobj の場合、this タイプは Person*です。

もう 1つ覚えておくべきことは、this ポインターは常に rvalue であり、変更できないことです。ただし、*thisthis ポインタを参照解除します。

十分な背景を経た後、サンプルコードを見て、this*this の違いを理解しましょう。

#include <iostream>
using namespace std;

class Counter {
 private:
  int Count;

 public:
  Counter() { this->Count = 0; }
  void IncreaseCount() { Count++; }
  void PrintCount() { cout << this->Count << endl; }
  Counter* GetCount_Pointer() { return this; }
  Counter GetCount_Copy() { return *this; }
  Counter& GetCount_Reference() { return *this; }
};
int main() {
  // Section-A
  cout << "Sectio-A" << endl;
  Counter C1;
  C1.IncreaseCount();
  Counter* CounterPtr =
      C1.GetCount_Pointer();  // CounterObj will be pointing to C1
  CounterPtr->IncreaseCount();
  C1.PrintCount();

  // Section-B
  cout << "Section-B" << endl;
  Counter C2;
  C2 = C1.GetCount_Copy();
  C1.IncreaseCount();
  C1.PrintCount();
  C2.PrintCount();

  // Section-C
  cout << "Section-B" << endl;
  Counter& CounterRef = C1.GetCount_Reference();
  CounterRef.PrintCount();

  return 0;
}

このコードスニペットは、GetCount メソッドへの複数のメソッドを持つ Counter クラスを作成します。最初のメソッド(つまり、GetCount_Pointer)は、単に呼び出し元オブジェクトのアドレスである this ポインターの値を返します。

GetCount_Copy メソッドは*this を返しますが、return タイプは Counter タイプのオブジェクトです。したがって、この関数は呼び出し元オブジェクトのディープコピーを返します。つまり、返されたコピーを変更しても、元のコピーには影響しません。

最後の GetCount_Reference メソッドも*this を返しますが、このメソッドの戻り型は参照オブジェクト(つまり、Counter&)です。新しいディープコピーを作成する代わりに、コンパイラは元のオブジェクトへのエイリアスまたは参照を返します。

エイリアスを介して行われた変更は、元のオブジェクトにも反映されます。

メインのドライバーコードについて説明する前に、プログラムの出力を見てみましょう。

出力:

Sectio-A
2
Section-B
3
2
Section-B
3

main メソッドのセクション A は、最初に Counter タイプの C1 オブジェクトを宣言し、IncreaseCount() を介してその Count をインクリメントします。後で、C1.GetCount_Pointer() によって返されたアドレスで CounterPtr ポインタを初期化します。

これで、CounterPtrC1 を指します。したがって、CounterPtr を介してインクリメント関数を呼び出すと、C1 も変更されます。

セクション B の C2 = C1.GetCount_Copy() は、C1.GetCount_Copy()C1 のコピーに置き換えられるため、C1 の現在のすべての内容を C2 に深くコピーします。したがって、C1 のカウントを増やしても C2 には影響しません。

セクション C は、Counter タイプへの参照変数を宣言し、C1.GetCount_Reference() によって返されるもので初期化します。C1.GetCount_Reference()C1 のエイリアスを返すため、CounterRefC1 の別名になります。

ステートメント Counter& CounterRef = C1.GetCount_Reference(); 論理的には Counter& CounterRef = C1 と同等になります。