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のコード例は、アトリビュートの指定位置が間違っている。
いや~、わかりやすい!
ReplyDeleteところで、
// [[hiding]] floag valeu ;
となっている箇所があります。
ここは、
// [[hiding]] float valeu ;
ですよね?
それから、
struct Derived : Base1, Base2
{
using Base1::f ;
void f [[hiding]] (double) {}
} ;
のケースですが、usingの方のfも、Base2のfを隠してますよね?
上記は問題ないですが、[[base_check]]を入れると大変そうですね。
と思ったら、
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3083.html
1063,1067
あたりに指摘されているのか。
しかし、これはメンバーに対してのチェックであるというのは、名前からも明らかであり、
ReplyDeleteそれをusing宣言にまで拡大するのは、どうかと思うのですが。
あーこれと良く似た話題がEffective-C++にあったような・・・
ReplyDeleteC++の書籍は随分(100冊以上)買ったけど、C++0xの本もたくさん買う事になるんだろうなあ
置き場所がないよ本当に