Class member name checking attributesというattributeがある。これは、override、hiding、base_checkからなるattributeである。これらは、ありがちなミスを防ぐために用意されている機能である。
overideの説明。例えば、以下のようなコードを考えてみる。
class Base
{
public :
virtual void do_something() {}
} ;
class Derived : public Base
{
public :
virtual void do_something() {}
} ;
ある名前の派生クラスの仮想関数に対して、同名の基本クラスの仮想関数がある時、派生クラスの仮想関数名は、基本クラスの仮想関数名をオーバーライドしていると言う。この場合では、Derived::do_somethingは、Base::do_somethingを、オーバーライドしている。では、Derivedが以下のように書かれていた場合はどうか。
class Derived : public Base
{
public :
// typo
virtual void do_somethig() {}
} ;
これをみると、Derivedの仮想関数が、do_somethigとなっている。これはtypoである。このコードを書いたプログラマの意図は、派生クラスのDerivedで、基本クラスの仮想関数、Base::do_somethingをオーバーライドしたいというものである。しかし、コンパイラーは、そんな人間側の都合など、理解できるはずがない。そこでこのコードは、全く問題なくコンパイルが通ってしまう。このバグは、大抵の場合、プログラムを実際に実行するまで発見出来ないのである。
あるいは、引数を間違えてしまうという場合もある。
class Base
{
public :
virtual void do_something(int, int, int) {}
} ;
class Derived : public Base
{
public :
virtual void do_something(int, int, short) {}
} ;
この場合も、やはりオーバーライドはされない。
このような場合、コンパイルエラーになって欲しい。ここで必要なのは、「このメンバー名は、必ずオーバーライドする。オーバーライドしない場合、それは誤りだ」という意味を表現できる文法である。overrideアトリビュートは、まさにその機能を提供する。Derivedの仮想関数は、以下のように書けばよい。
class Derived : public Base
{
public :
// コンパイルエラーとなる
virtual void do_somethig [[override]] () {}
// 以下のように書くこともできる
// 上と同じ意味となる。どちらを使っても良い。
// [[override]] virtual void do_somethig () {}
} ;
overrideアトリビュートの指定されたメンバーが、実際には、何もオーバーライドしていない場合、エラーとなる。これにより、プログラマの単純なミスを防ぐことができる。
hidingの説明。以下のような例を考える。
struct Base
{
int value ;
} ;
struct Derived : Base
{
float value ;
} ;
ある名前の派生クラスのメンバーに対して、同名の基本クラスのメンバーがある場合、派生クラスのメンバー名が、基本クラスのメンバー名を、隠す(hiding)と言う。
もし、Derivedが以下のように書かれていた場合、
struct Derived : Base
{
// typo
float valeu ;
} ;
この場合、Derived::valeuは、Base::valueを隠さない。しかし、これは問題なくコンパイルが通ってしまう。このバグは、大抵の場合、実行時に発見される。ここでも、コードの中で、このメンバー名は、基本クラスのメンバーを隠すのだという意味を、明示的に記述できれば、コンパイルエラーをだせる。hiding アトリビュートは、そのためにある。
struct Derived : Base
{
// typo
float valeu [[hiding]] ;
// 以下のように書くこともできる
// [[hiding]] float valeu ;
} ;
hidingアトリビュートが指定されたメンバー名が、同名の基本クラスのメンバーを隠していない場合、エラーとなる。
ところで、この「隠す」という概念には、注意が必要である。たとえば、
struct Base
{
void f(int) {}
} ;
struct Derived : Base
{
void f(double) {}
} ;
int main()
{
Derived d ;
d.f(0) ; // Derived::fが呼ばれる
}
この場合、Derived::fは、Base::fを隠してしまう。メンバーは隠されているので、オーバーロード解決の際に、考慮されない。従って、Derived::fには、hidingが使える。
struct Derived : Base
{
void f(double) {}
} ;
しかし、C++では、using宣言で、基本クラスのメンバー名を引っ張り出すことができる。
struct Derived : Base
{
using Base::f ;
// エラー、Derived::fは何も隠してない
void f [[hiding]] (double) {}
} ;
では、同名のusing宣言があれば、メンバー名は何も隠していないと言えるだろうか。実は、そうとも限らない。なぜなら、C++には、多重継承というものがある。
struct Base1
{
void f(int) {}
} ;
struct Base2
{
void f(short) {}
} ;
struct Derived : Base1, Base2
{
using Base1::f ;
void f [[hiding]] (double) {}
} ;
この場合、Derived::fは、Base1::fは隠していないものの、依然として、Base2::fは隠している。従って、hidingを指定しても、エラーにはならない。
隠しているかどうかというのは、using宣言にも左右されるので、注意しなければならない。
base_checkの説明。overrideとhidingは、つまらない間違いを、コンパイル時に未然に防ぐという、極めて便利な機能を提供している。しかし、実は名前を隠す意図がないのに、うっかりと隠してしまっていた場合は、検出できない。
// 同僚の大阪人によって書かれた基本クラス
struct Base
{
// 何かユニークな名前が必要や。これならどやろ。
// まさかこんな珍妙な名前を使う奴はおらんやろ
int ケツの穴から手ぇ突っ込んで奥歯ガタガタいわしたろか ;
} ;
// 別の大阪人によって書かれた派生クラス
struct Derived : Base
{
// Baseがどんな実装になってるかは知らんが、とりあえず衝突しない名前が必要や。
// これならええやろ。まさかワシと同じ考えの奴はおるまい。
int ケツの穴から手ぇ突っ込んで奥歯ガタガタいわしたろか ;
} ;
この例では、たまたま二人の大阪人のセンスが同じであったため、ユニークにしようと思ってつけた長い名前が、衝突してしまっている。これは、明示的な指定では、解決できない。
そこで、base_checkというアトリビュートが用意されている。これは、ある派生クラスに指定することで、overrideとhidingをしているメンバー名に対しては、明示的にアトリビュートを書かなければ、エラーとなる機能を持つ。
struct Base
{
virtual void f(int) {}
void g(int) {}
} ;
struct [[base_check]] Derived : Base
{
// OK、overrideがある。
virtual void f [[override]] (float) {}
// エラー、overrideが必要なのに、指定されていない
virtual void f (double) {}
// OK、hidingがある
void g [[hiding]] (short) {}
// エラー、hidingが必要なのに、指定されていない
viud g(double) {}
// OK、overrideもhidingも必要ない
void I_am_the_one() {}
} ;
注意:N3092のbase_checkのコード例は、アトリビュートの指定位置が間違っている。