C++のthisについて

AI実装検定のご案内

目次

C++のthisとは

C++のthisは、現在そのメンバ関数を実行しているオブジェクト自身を指すポインタです。

通常の非staticメンバ関数の中で使用でき、メンバ変数へのアクセスや、自分自身を返す処理などに利用されます。

例えば、次のコードを見てみましょう。

class Sample {
public:
    void show() {
        // thisはshow()を呼び出したオブジェクトを指す
    }
};

int main() {
    Sample obj;
    obj.show();
}

obj.show()を呼び出した場合、show()内のthisobjを指します。

そのため、thisは「現在処理しているオブジェクトを指すポインタ」と理解すると分かりやすいでしょう。

thisはポインタである

thisはオブジェクトそのものではなく、オブジェクトへのポインタです。

例えば、Sampleクラスの通常の非constメンバ関数であれば、thisは概念的に次の型になります。

Sample*

したがって、メンバ変数へアクセスするときは次のように記述できます。

this->value

これは、次の記述と同じ意味です。

(*this).value

thisはポインタなので、通常は->演算子を使ってメンバへアクセスします。

thisの基本的な使い方

メンバ変数へアクセスする

thisを使うと、現在のオブジェクトが持っているメンバ変数へ明示的にアクセスできます。

#include <iostream>

class Person {
private:
    int age;

public:
    void setAge(int value) {
        this->age = value;
    }

    void showAge() const {
        std::cout << this->age << std::endl;
    }
};

このコードでは、次の部分が現在のオブジェクトのageを表しています。

this->age

ただし、通常はthis->を省略できます。

そのため、次の2つは基本的に同じ意味です。

this->age = value;
age = value;

thisでメンバ変数と引数を区別する

引数とメンバ変数が同じ名前の場合

thisがよく使われる場面の一つが、メンバ変数と関数の引数が同じ名前になっているケースです。

class Person {
private:
    int age;

public:
    void setAge(int age) {
        this->age = age;
    }
};

このコードでは、左側のthis->ageがメンバ変数です。

this->age

右側のageは関数の引数です。

age

したがって、次のコードは「現在のオブジェクトのageへ、引数ageの値を代入する」という意味になります。

this->age = age;

もし単純に次のように書くと、両方とも引数ageとして解釈されます。

age = age;

そのため、同じ名前を使用するときにはthis->によって区別できます。

コンストラクタでthisを使う方法

コンストラクタ内でメンバへ代入する

コンストラクタでもthisを使用できます。

#include <string>

class User {
private:
    std::string name;
    int age;

public:
    User(std::string name, int age) {
        this->name = name;
        this->age = age;
    }
};

この場合も、this->namethis->ageはメンバ変数を表しています。

一方、右側のnameageはコンストラクタの引数です。

メンバ初期化リストを使う方法

コンストラクタでは、単純に代入するよりもメンバ初期化リストを使用するほうが適切なケースが多くあります。

class User {
private:
    std::string name;
    int age;

public:
    User(std::string name, int age)
        : name(name), age(age) {
    }
};

この記述では、メンバ変数を直接初期化できます。

特にstd::stringなどのクラス型をメンバに持つ場合は、コンストラクタ本体で代入するよりも初期化リストを使うのが一般的です。

thisと*thisの違い

thisは自分自身へのポインタ

thisは現在のオブジェクトを指すポインタです。

例えば次のメンバ関数では、自分自身へのポインタを返しています。

class Sample {
public:
    Sample* getThis() {
        return this;
    }
};

戻り値の型は次のようになります。

Sample*

*thisは現在のオブジェクトを表す

一方、*thisthisをデリファレンスしたものです。

つまり、現在のオブジェクトを表します。

class Sample {
public:
    Sample& getSelf() {
        return *this;
    }
};

この場合、戻り値はSample&です。

整理すると、次のようになります。

記述意味
this現在のオブジェクトを指すポインタ
*this現在のオブジェクトを表すlvalue
this->value現在のオブジェクトのvalue
(*this).valuethis->valueと同じ
return this;自分自身へのポインタを返す
return *this;自分自身を参照などとして返す

return *thisで自分自身を返す

メソッドチェーンを実装する

return *thisを利用すると、現在のオブジェクト自身への参照を返せます。

この仕組みを利用すると、複数のメンバ関数を連続して呼び出すメソッドチェーンを実装できます。

#include <iostream>

class Counter {
private:
    int value = 0;

public:
    Counter& add(int n) {
        value += n;
        return *this;
    }

    void show() const {
        std::cout << value << std::endl;
    }
};

int main() {
    Counter counter;

    counter.add(10)
           .add(20)
           .add(30);

    counter.show();
}

実行結果は次のようになります。

60

add()が毎回*thisを参照として返しているため、次のように連続して呼び出せます。

counter.add(10).add(20).add(30);

thisと演算子オーバーロード

代入演算子でreturn *thisを使う

this*thisは、演算子オーバーロードでもよく使われます。

代表的なのが代入演算子です。

class Sample {
private:
    int value = 0;

public:
    Sample& operator=(const Sample& other) {
        value = other.value;
        return *this;
    }
};

代入演算子が自分自身への参照を返すことで、次のような連続代入が可能になります。

a = b = c;

この式は、結合規則によって次のように解釈されます。

a = (b = c);

b = cの結果としてbへの参照が返されるため、その結果をさらにaへの代入に使用できます。

thisで自己代入を判定する

thisと&otherを比較する

コピー代入演算子では、thisを利用して自己代入を判定することもできます。

class Sample {
private:
    int value = 0;

public:
    Sample& operator=(const Sample& other) {
        if (this != &other) {
            value = other.value;
        }

        return *this;
    }
};

thisは代入先オブジェクトのアドレスです。

一方、&otherは代入元オブジェクトのアドレスです。

そのため、次の条件で同じオブジェクトかどうかを判定できます。

this != &other

例えば、次のようなコードです。

Sample a;
a = a;

自己代入チェックは必須とは限らない

ただし、現代的なC++では自己代入チェックが必ず必要というわけではありません。

例えば、単純に整数を代入するだけなら次のコードでも問題なく自己代入できます。

Sample& operator=(const Sample& other) {
    value = other.value;
    return *this;
}

また、std::stringstd::vectorなどをメンバとして使用している場合は、コピー代入演算子そのものを自分で実装せず、コンパイラが生成するものに任せられるケースもあります。

自己代入判定は、thisの活用例の一つとして理解するとよいでしょう。

constメンバ関数とthisの関係

constメンバ関数ではthisがconstポインタとして扱われる

次のように、メンバ関数の末尾にconstを付けることがあります。

class Sample {
private:
    int value = 10;

public:
    int getValue() const {
        return value;
    }
};

このようなconstメンバ関数では、thisは概念的に次の型として扱われます。

const Sample*

そのため、通常のメンバ変数を書き換えることはできません。

int getValue() const {
    value = 100;  // エラー
    return value;
}

constメンバ関数は、基本的にオブジェクトの状態を変更しない関数として利用します。

mutableメンバは例外

ただし、mutableを指定したメンバ変数はconstメンバ関数からでも変更できます。

class Sample {
private:
    mutable int count = 0;

public:
    void update() const {
        ++count;
    }
};

このように、constメンバ関数だからすべてのデータメンバを絶対に変更できないというわけではありません。

staticメンバ関数ではthisを使えない

static関数には現在のオブジェクトが存在しない

staticメンバ関数ではthisを使用できません。

class Sample {
public:
    static void test() {
        // thisは使用できない
    }
};

staticメンバ関数は特定のオブジェクトに属する処理ではなく、クラスそのものに関連付けられた関数です。

そのため、次のようにオブジェクトを作らなくても呼び出せます。

Sample::test();

特定の呼び出し元オブジェクトが存在しないため、現在のオブジェクトを指すthisもありません。

thisを省略できる理由

通常のメンバアクセスではthis->を書かなくてもよい

通常の非staticメンバ関数では、メンバ変数へアクセスするときにthis->を省略できます。

例えば、次のコードがあります。

class Sample {
private:
    int value;

public:
    void setValue(int n) {
        value = n;
    }
};

ここで使われている、

value = n;

は、実質的には次のようなメンバアクセスです。

this->value = n;

そのため、名前の衝突などがなければthis->を毎回書く必要はありません。

テンプレートではthis->が必要になる場合がある

依存基底クラスのメンバへアクセスする場合

通常はthis->を省略できますが、テンプレートでは明示的に書かなければならないケースがあります。

代表的なのが、テンプレートの依存基底クラスです。

template <typename T>
struct Base {
    int value = 10;
};

template <typename T>
struct Derived : Base<T> {
    void show() {
        this->value = 20;
    }
};

このようなコードでは、Base<T>がテンプレート引数に依存する基底クラスなので、valueを単純に書くだけでは正しく名前解決されない場合があります。

そこで、次のように記述します。

this->value

C++のテンプレートを扱うようになると、this->は単なる可読性のためだけではなく、言語仕様上必要になるケースがあることも覚えておくとよいでしょう。

thisのアドレスを確認する方法

オブジェクトのアドレスとthisは一致する

thisが実際にどのオブジェクトを指しているのかは、アドレスを表示すると確認できます。

#include <iostream>

class Sample {
public:
    void showAddress() const {
        std::cout << "this = " << this << std::endl;
    }
};

int main() {
    Sample obj;

    std::cout << "&obj = " << &obj << std::endl;
    obj.showAddress();
}

実行すると、例えば次のような結果になります。

&obj = 0x7ffd12345678
this = 0x7ffd12345678

実際のアドレスは環境によって異なりますが、&objthisが同じ値になることを確認できます。

これはthisobj自身を指しているためです。

複数のオブジェクトがある場合のthis

呼び出したオブジェクトによってthisが変わる

同じクラスから複数のオブジェクトを作成すると、それぞれのメンバ関数内でthisが指す対象も変わります。

#include <iostream>

class Sample {
public:
    void showAddress() const {
        std::cout << this << std::endl;
    }
};

int main() {
    Sample a;
    Sample b;

    a.showAddress();
    b.showAddress();
}

a.showAddress()の実行中はthisaを指します。

一方、b.showAddress()の実行中はthisbを指します。

この仕組みによって、同じメンバ関数でも、それぞれ異なるオブジェクトの状態を操作できます。

C++23の明示的オブジェクトパラメータ

C++23ではthisを使わないメンバ関数も記述できる

C++23では、明示的オブジェクトパラメータという機能が追加されています。

例えば次のように記述できます。

struct Sample {
    void show(this Sample& self) {
        // selfが呼び出し元オブジェクト
    }
};

この形式では、従来の暗黙的なthisの代わりに、呼び出し元オブジェクトをselfなどの名前で明示的に受け取ります。

そのため、このメンバ関数の中では通常のthisは使用できません。

deducing thisとして活用できる

テンプレートと組み合わせると、次のように記述できます。

struct Sample {
    template <typename Self>
    void show(this Self&& self) {
        // selfの型を推論できる
    }
};

この仕組みは一般に「deducing this」と呼ばれます。

オブジェクトのconst性や値カテゴリなどをテンプレートとして扱えるため、重複したメンバ関数を減らせる場合があります。

ただし、C++23以降の比較的新しい機能なので、まずは従来のthisを理解してから学ぶとよいでしょう。

C++のthisを理解するためのポイント

C++のthisを理解するときは、次の3つを押さえることが重要です。

まず、thisは現在のオブジェクトを指すポインタです。

this

次に、*thisは現在のオブジェクトを表します。

*this

そして、現在のオブジェクトのメンバへ明示的にアクセスするときは次のように記述します。

this->member

特に実際のプログラムでは、メンバ変数と引数の区別、メソッドチェーン、演算子オーバーロード、テンプレートなどでthisが登場します。

また、通常のconstメンバ関数ではthisconst付きのポインタとして扱われ、staticメンバ関数ではthisそのものが存在しません。

C++23では明示的オブジェクトパラメータも導入されていますが、基本となる考え方は「どのオブジェクトに対してメンバ関数が実行されているのか」を理解することです。

this*thisthis->memberの違いを理解しておけば、C++のクラスやオブジェクト指向プログラミングをより深く理解しやすくなります。

以上、C++のthisについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次