18
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++Advent Calendar 2023

Day 7

Deducing thisで子クラスにも繋がるメソッドチェーン

Last updated at Posted at 2023-12-06

はじめに

C++23から使える Deducing thisを使った、ちょっとしたテクニックを紹介します。

もし、あなたがメソッドチェーンを使ったコーディングを好むなら、thisを返すようなメソッドを作ったことがあったかもしれません。
しかし継承が絡むと順序を気を付ける必要が出てきます。

問題になるケース

例として以下のコードを見てみましょう。

class Actor
{
public:
    Actor& setId(int id)
    {
        this->m_id = id;
        return *this;
    }
private:
    int m_id;
};

class Hero : public Actor
{
public:
    Hero& setHp(int hp)
    {
        this->m_hp = hp;
        return *this;
    }
private:
    int m_hp;
};

以下のような順番でのメソッドチェーンは問題ないですが、

Hero hero;
hero
    .setHp(10)
    .setId(1)
    ;

メソッドを呼ぶ順番を変えると、繋がらなくなります。

Hero hero;
hero
    .setId(1)
    // .setHp(10) setIdの戻り値は Actor& なので setHp へ繋げられない
    ;
hero.setHp(10);

これは当然の結果ですが、親クラスのメソッドを呼んだ時点で、子クラスのメソッドへは繋げることができなくなります。

今回の例くらいなら、順番を気を付けるだけでいいかもしれませんが、
どうしても親クラスのメソッドを先に呼びたいケースがあるとすれば
メソッドチェーンで気持ちよくなりたい あなたは チェーンが途切れることにモヤモヤするかもしれません。

ここでも使えた Deducing this

親クラスのチェーンするメソッドを修正してみましょう。

    template<class Self>
    Self& setId(this Self& self, int id)
    {
        self.m_id = id;
        return self;
    }

こうする事で、子クラスから呼んだ場合の関数の戻り値が子クラスの型になります。

仮想関数(virtual)の場合はDeducing thisが使えないことには注意

よって以下のコードが成功します。

Hero hero;
hero
    .setId(1)
    .setHp(10) // setIdの戻り値は Hero& なので setHp へ繋がる
    ;

もちろん子クラスが複数でも問題ありません。

class Actor
{
public:
    template<class Self>
    Self& setId(this Self& self, int id)
    {
        self.m_id = id;
        return self;
    }
private:
    int m_id;
};

class Hero : public Actor
{
public:
    Hero& setHp(int hp)
    {
        this->m_hp = hp;
        return *this;
    }
private:
    int m_hp;
};

class Enemy : public Actor
{
public:
    Enemy& setExp(int exp)
    {
        this->m_exp = exp;
        return *this;
    }
private:
    int m_exp;
};

int main()
{
    Hero hero;
    hero
        .setId(1)
        .setHp(10)    // setIdの戻り値は Hero& なので setHp へ繋がる
        ;
    Enemy enemy;
    enemy
        .setId(2)
        .setExp(100) // setIdの戻り値は Enemy& なので setExp へ繋がる
        ;
}

これで継承関係があっても自由に気持ちよくメソッドチェーンを繋げられます。

まとめ

Deducing thisを使ったテクニックとして
親クラスのメソッドを先に呼んでも子クラスのメソッドにチェーンする方法を紹介しました。

  • これまでthisを返すようなメソッドチェーンのパターンで継承関係があると順序を気を付ける必要があった
    • 親クラスに定義したthisを返すメソッドを子クラスから呼んでも戻り値が親クラスの型になるので、子クラスのメソッドにチェーンできない
  • Deducing thisを使うことで上記問題を解決できる
    • 親クラスに定義したthisを返すメソッドを子クラスから呼んだら戻り値が子クラスの型になるので、子クラスのメソッドにチェーンできる
    • ただし仮想関数の場合はできない
  • 気持ちよいメソッドチェーンライフを送れる

参考

そもそもDeducing thisとはなんぞやみたいな話は以下が参考になります。

18
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?