[C++] override すべき関数の名前 typo に気づける idiom
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-override
は override
キーワードをつけるべきところにキーワードがないことを警告してくれます。
これにより意図しないオーバーライドや 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() {}
Discussion