2010-07-20

Class member name checking attributes

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のコード例は、アトリビュートの指定位置が間違っている。

3 comments:

redboltz said...

いや~、わかりやすい!

ところで、
// [[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
あたりに指摘されているのか。

江添亮 said...

しかし、これはメンバーに対してのチェックであるというのは、名前からも明らかであり、
それをusing宣言にまで拡大するのは、どうかと思うのですが。

萌ゑ said...

あーこれと良く似た話題がEffective-C++にあったような・・・

C++の書籍は随分(100冊以上)買ったけど、C++0xの本もたくさん買う事になるんだろうなあ

置き場所がないよ本当に