🎗

[C++] override すべき関数の名前 typo に気づける idiom

2020/09/28に公開

C++ の仮想関数オーバーライドでよくあるミス

  • オーバーライドしていたつもりができてなかった
    • 仮想関数がいつのまにか変わっていた
    • const / reference など微妙に違ってた
    • 名前を間違えた
  • 意図せずオーバーライドしてしまっていた

基本的にはオーバーライド「したい・したくない」と「した・してない」が一致しないときに問題となります。

コンパイラ/開発者 したい したくない
する 👌 😱
しない 😱 👌

override キーワード

C++11 以降であれば override をつけるようにすれば OK です。
overrideとfinal - cpprefjp C++日本語リファレンス
「オーバーライドするつもりが、できていなかった」はこれで解決です。

class Test
{
public:
    virtual void SetUp() {} // ユーザーが拡張可能なポイント
};

class OverrideTest : public Test
{
public:
    void SetUp() override {}
};

int main() {}

override キーワードにより仮想関数の引数が変わった場合、エラーになるので便利です。
ただし、 override をつけなくてもコンパイルはできてしまうので override をつけ忘れてしまうとダメです。

-Wsuggest-override

-Wsuggest-overrideoverride キーワードをつけるべきところにキーワードがないことを警告してくれます。
これにより意図しないオーバーライドや override のつけ忘れに気づくことができます。

class Test
{
public:
    virtual ~Test() {}
    virtual void SetUp() {} // ユーザーが拡張可能なポイント
};

class OverrideTest : public Test
{
public:
    virtual ~OverrideTest() {}
    void SetUp() {} // -Wsuggest-override
};

int main() {}

警告ではなく -Werror=suggest-override でエラーにすることで、 override をつけることを強制しても良いと思います。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

-Winconsistent-missing-override

似たような警告に -Winconsistent-missing-override があります。
こちらは一貫性のない override ということで、全部に override がついてない場合は警告されません。 override を使っているのに override がついていない場合に警告されます。
こちらは clang 3.6 以降でデフォルト有効になっています。(GCC はなし)

class Test
{
public:
    virtual void SetUp() {} // ユーザーが拡張可能なポイント
    virtual void TearDown() {}
};

class OverrideTest : public Test
{
public:
    void SetUp() {} // -Winconsistent-missing-override
    void TearDown() override {}
};

int main() {}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
override が 1 つも使われていないとダメですがこちらも有益です。

C++03 の場合

太古の C++ では override がありません。
昔の人はいったいどうやってこの問題と立ち向かっていたのでしょうか?

ここからが本題。
Google Test がこの問題に対処しているのに気付いたので紹介します。

class Test
{
public:
    virtual void SetUp() {} // ユーザーが拡張可能なポイント
private:
    struct Setup_should_be_spelled_SetUp {};
    virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; } // override して欲しい関数とは違う戻り値にしておく
};

方法はいたってシンプルで、typo しやすい名前の virtual 関数をユニークな戻り値(private な構造体ポインター)で定義することです。
ユーザーが void Setup() のように誤った名前で関数定義すると、Setup をオーバーライドしようとするものの戻り値が異なるためエラーになります。

サンプルコード

こちらがサンプルコードになります。
C++11 以降の場合 (Wandbox) は、 override が使えるのでそれで問題なし。
C++03 の場合 (Wandbox) は、 本 Idiom によって Setup の typo はコンパイル時に防げるようになりました。
ただし、 setup と typo された場合は防げません。

#include <stdio.h>

class Test
{
public:
    virtual void SetUp() {} // ユーザーが拡張可能なポイント
private:
    struct Setup_should_be_spelled_SetUp {};
    virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } // override して欲しい関数とは違う戻り値にしておく
};

#if __cplusplus >= 201103L
class OverrideTest : public Test
{
public:
    void SetUp() override {} // OK
    void Setup() override {} // override するものがないので NG
    void setup() override {} // override するものがないので NG
};
#endif

class NoOverrideTest : public Test
{
    void SetUp() {} // OK
    void Setup() {} // return type 違いでエラー
    void setup() {} // OK, だけど SetUp を期待している場合は期待通り動かない
};

int main() {}

C++03 なんてもう誰も使ってないから役に立たないのでは?

そんなことはありません。
まだ使ってます。

override をつけるルールを強制すること(-Werror=suggest-override)はライブラリ開発者からはできません。
使う側の注意で防げる問題ではありますが、使ってもらう側としてはこのような方法で対策しておくのがベターと言えるでしょう。

蛇足

final でも可能。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

class Test
{
public:
    virtual void SetUp() {} // ユーザーが拡張可能なポイント
private:
    virtual void Setup() final {}
};

class NoOverrideTest : public Test
{
    void SetUp() {} // OK
    void Setup() {} // final な関数をオーバーライドしようとした
    void setup() {} // OK, だけど SetUp を期待している場合は期待通り動かない
};

int main() {}
GitHubで編集を提案

Discussion