gcc 4.7は、finalとoverrideを実装している。
finalとは、クラスの派生と、virtual関数のオーバーライドを禁止するための機能である。
struct B final { } ; struct D : B { } ; // エラー struct C { virtual void f() final { } } ; struct D : C { void f() { } // エラー、C::fはオーバーライドできない } ;
これ以上説明する必要がないくらいに分かりやすい機能だ。現実的には、最底辺の基本クラスのvirtual関数のオーバーライドを禁止にするということはない。なぜならば、それは普通の関数とほぼかわりないからだ。あえて言えば、派生クラスで、同じシグネチャの関数を定義できなくする程度の意味しかない。大方、以下のような目的に使われるだろう。
struct A { virtual void f() = 0 ; } ; struct B : A { // 最終的な実装 virtual void f() final { /* 実装 */} } ; // Bからは、まだ派生できるが、オーバーライドはさせない。 struct C : B { } ;
overrideは、「俺はオーバーライドするぞジョジョッ!」と明示的に叫ぶための機能である。overrideを指定しておきながら、実際にオーバーライドをしていないvirtual関数はエラーとなる。
struct Base { virtual void hoge() { } } ; struct Fail : Base { void hage() override { } // エラー、オーバーライドしていない } ;
よくみると、Failのメンバー関数の名前は、hogeではなくhageになっている。そのため、hageは関数をオーバーライドしない。よってエラーとなる。
ご存知のように、virtual関数をオーバーライドするさいには、virtualキーワードは必要ない。しかし問題は、名前を間違えたり、仮引数や戻り値の型を間違えたりすると、別の関数になってしまう。
struct Base { virtual void func(int) { } } ; struct Fail : Base { // Base::hoge()をオーバーライドしたい void fucn(int) ; // 名前間違い void hoge(long) ; // 仮引数の型間違い } ;
コンパイラーは喜んでこのコードを受け取り、別の関数として解釈してしまう。コンパイルエラーにならないのはもちろんのこと、警告を発するのも難しいのだ。たとえvirtualキーワードを使っていても、問題は変わらない。普通の関数ではなく、新しいvirtual関数を導入するだけだからだ。
間違いを防ぐためには、オーバーライドするということを明示的に示さなければならない。overrideはそのためのキーワードである。
完全を期すために付け加えると、finalとoverrideを組み合わせることもできる。
struct B { virtual void f() = 0 ; } ; struct D : B { void f() final override { } } ;
意味はもちろん、finalとoverrideの両方である。
この新機能は、純粋にコンパイル時の些細なエラーを見つけやすくするための機能である。
この便利な機能がここ至るまでには、かなりの紆余曲折を経ている。そもそも、この機能はもともと、attributeを使って実現されるはずだったのだ。
// C++0xでは却下された機能 struct B { virtual void f() = 0 ; } ; class C [[final]] { [[final, override]] void f() { } } ;
しかし、色々と議論した挙句、標準機能は独自のキーワードを与えられてしかるべきだというコンセンサスが得られた。しかし、一体どんなキーワードを使えばいいというのか。キーワードの追加は、互換性の敵である。既存のコードで使われていない識別子を選ぶ必要があるが、あまりに変な名前では、意味が分からない。finalというのは、他の言語の同等機能に与えられているキーワードである。しかし、さすがにfinalなどというキーワードを今更C++に付け加えることはできない。既存のコードを破壊してしまうからである。
苦渋の末、標準化委員会は、Contextual Keywordを使う決断をした。これは、ある特定の文脈で現れた時のみ、意味をなすキーワードである。
たとえば、C++の文法上、クラス指定子のクラス名の後に識別子が来ることはない。
class Name /*ここに識別子は使えない*/ : Base { } ;
とすれば、その場所だけで有効なキーワードを作ることができる。これは文法全体には影響しないので、finalという識別子は、他の場所では問題なく使える。
struct final final { } final ; struct X final { } ;
本来、この機能にはもう一つ、hidingというキーワードが加わるはずだった。これは、attributeからcontextual keywordに移行するときに、newを再利用することになった。そして、最後の最後になって、ドラフトから削られた。
このhidingは、名前を隠すということを明示的に主張する機能である。
struct A { int value ; void f( int ) ; } ; struct B : A { int value ; // A::valueを隠す void f( double ) { } // A::fを隠す } ; int main() { B b ; b.value = 0 ; // b.B::balue b.f( 0 ) ; // b.B::f }
この、派生クラスのスコープが基本クラスのスコープ内の名前を隠すという挙動は、時として不思議な結果を招くことがある。また、うっかりして基本クラスのメンバーを隠してしまわないとも限らない。そのため、隠すということを明示的に主張するための、hidingが、finalやoverrideと同時に提案された。hidingが指定された名前が、実際には何も隠していない場合は、エラーとなる。
// C++0xでは却下された機能 struct A { int value ; } struct B : A { int vlaue new ; // エラー } ;
B::vlaueは、typoである。単純な間違いだが、現実に起こりうる間違いである。しかもコンパイラーは警告を発することができない。hidingは、overrideと同じように、明示的な隠す主張を行えるようにした。
また、このoverrideとhidingを明示的に使わなければエラーとなる、explicitも提案された。
// C++0xでは却下された機能 struct A { int value ; virtual void f() = 0 ; } ; struct B explicit : A { int value ; // エラー、newが指定されていない void f() { } // エラー、overrideが指定されていない } ;
何が問題だったかというと、hidingは、文法上、かなり広範な範囲で起こるのだが、既存の文法を虱潰しに対応させるだけの余裕がなかったのである。たとえば、using宣言が関係する場合はどうなるのか。
struct A { void f(int) ; } ; struct B : A { using A::f ; void f(double) ; } ;
この場合、名前は隠されていない。しかし、using宣言で名前を隠すこともできる。
struct A { void f(int) ; } ; struct B { void f(double) ; } ; struct C : A, B { using A::f ; // B::fを隠す }
この機能が提案されたのは、規格がほぼ固まった後であり、到底対応する時間はなかった。また、hidingのために、newキーワードを再利用するというのは、あまり賛同を得られなかった。結局、色々と議論した挙句、今、早急にhidingを言語仕様に取り入れるのは、将来のためによろしくないと結論した。そこで、hidingが取り除かれ、それに関連して、explicitも取り除かれた。なぜなら、hidingなしでexplicitだけあっても、片手落ちであるし、将来hidingを取り入れた時に、深刻な互換性の問題を引き起こすからである。
色々あって、本来の機能の半分以上がそがれてしまい、finalとoverrideだけが残ったのである。
No comments:
Post a Comment
You can use some HTML elements, such as <b>, <i>, <a>, also, some characters need to be entity referenced such as <, > and & Your comment may need to be confirmed by blog author. Your comment will be published under GFDL 1.3 or later license with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.