62
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++のconstメンバ関数の挙動を直感的に理解する

Posted at

C++のconstメンバ関数ってなんとなく「そのメンバ関数の呼び出しによってオブジェクトの内容を変更しないことを宣言する」程度に理解していて、オブジェクトの内容を変更しないというのが具体的にどういう操作なのかきちんと理解していませんでした。

そのため、例えばなぜこれがエラーで:

Hoe.h
class Hoe {
public:
    Hoe() {
    }
    
    virtual ~Hoe() {
    }

    void func() {
        ...
    }
};
Fuga.h
class FugaNG {
private:
    Hoe _hoe;
    
public:
    FugaNG() : _hoe() {
    }
    
    virtual ~FugaNG() {
    }
    
    void callHoeFunc() const {
        _hoe.func(); // ← コンパイルエラー!
    }
};

これがエラーにならない:

FugaOK.h
class FugaOK {
private:
    Hoe *_pHoe;
    
public:
    FugaOK() {
        _pHoe = new Hoe();
    }
    
    virtual ~FugaOK() {
    }
    
    void callHoeFunc() const {
        _pHoe->func(); // ← エラーにならない
    }
};

のか、知識としては分かっていても理屈が分かっていませんでした。

で、いろいろ調べたところ、以下のように考えるのが一番直感的だということが分かりました。(C++に詳しい人にとっては当たり前のことなのかもしれませんが…。)

「constメンバ関数では、暗黙的に渡されるthisポインタがconstで修飾される」

具体的に見ていきます。

前提

メンバ関数が呼び出される時、自分自身へのポインタであるthisが暗黙的に渡されるわけですが、例えば Hoe#func() についてこれを便宜上次のように書くことにします:

void func([Hoe *this]) {
    ...
}

オブジェクト経由でのメンバ関数を呼び出すケース

FugaNG#callHoeFunc() について考えてみます。このメンバ関数はconstメンバ関数ですので、渡されるthisポインタは次のようにconstで修飾されます:

void callHoeFunc([const FugaNG *this]) {
    _hoe.func();
}

ここで、_hoe.func() は実は this->_hoe.func() で、さらに上記の表記法で書くとこの呼び出しは this->_hoe.func([&this->_hoe]) となりますが、thisがconst修飾されていることでconst修飾が波及して &this->_hoe もconst修飾された const Hoe * になるため、呼び出そうとしているメンバ関数のシグネチャは

void func([const Hoe *this])

である必要があります。ところが実際は func() は非constなメンバ関数であるためシグネチャは

void func([Hoe *this])

となり、一致せずエラーになるというわけです。

ポインタ経由でのメンバ関数を呼び出すケース

一方、FugaOK#callHoeFunc() についてはどうでしょう。このメンバ関数も同じくconstメンバ関数ですので、渡されるthisポインタは次のようにconstで修飾されます:

void callHoeFunc([const FugaOK *this]) {
    _pHoe->func();
}

ここで、_pHoe->func() は実は this->_pHoe->func() で、さらに上記の表記法で書くとこの呼び出しは this->_pHoe->func([this->_pHoe]) となります。

さて this->pHoe の型はどうなるでしょう。thisがconst修飾されていることで this->_pHoe もconst修飾されてconst Hoe * になると思いますか?実はそうはなりません。const参照はされますが、Hoe * const のように修飾されます。これは、こうしないとthisが指すオブジェクトの中身が保護できないからです。

仮に this->_pHoeconst Hoe * のように修飾されてしまうと、_pHoe が指す先は保護されますが _pHoe 自体は変更可能ということになってしまい、目的が達せられないことになります。_pHoe 自身を保護するために Hoe * const のように修飾されるというわけです。

続けましょう。

Hoe * constconst は関数のシグネチャには影響しませんので、呼び出そうとしているメンバ関数 func() のシグネチャは

void func([Hoe *this])

となります。このため無事 Hoe#func() が呼び出せるというわけです。

まとめ

constメンバ関数の挙動を考える時は、次の原則を頭に置いておけば分かりやすいです。

  • constメンバ関数では、暗黙的に渡されるthisポインタがconstで修飾される
  • const修飾は波及する

「全然直感的じゃないじゃん!」と思われた方、すみません…。

62
53
1

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
62
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?