livaの雑記帳

livaの雑記帳

OSとか作ってみたい

c++でfinalつけれる時はつけよう、という話

仮想関数を使うとvtableを呼び出すからオーバーヘッドが生じるのは当たり前の話。だから仮想関数にしなくて良い時は仮想関数を使いたくない。

一方で、インタフェースを明示するためには仮想関数を使わなければいけない。インターフェースをソース内で明示したいだけなのに仮想関数のオーバーヘッドが生じるのも癪だなぁと思ってたら、finalつければいいじゃん、となったのでまとめておく。

 

先に書いておくと、たぶん殆どのc++プログラマにとってはあんまり旨味のない話のはず。

 

まずどういう状況かというと、Make時のマクロで使うソースを切り替えたい。

具体的には、aとbという2つのディレクトリがあって、それぞれにtest.hがある。

a/test.h

b/test.h 

 aのディレクトリのtest.hを使うか、bのディレクトリのtest.hを使うかはmake時のマクロ指定で決める事とする。

たぶんこの時点で普通のプログラミングではあまり起きない状況だろう。

 

それぞれのtest.hには異なるTestクラスがある。そしてこのTestクラスのインターフェースをコードレベルで明示したい。

普通に考えればTestInterfaceクラスを作って、それぞれのTestクラスがTestInterfaceを継承すれば良い。でも、test.hの外から使う時はTestInterfaceではなく、Testクラスなので、インターフェース継承によってオーバーヘッドが生じるのはバカバカしい。(型によって動的に実行コードが切り替わらないなら、vtableを使わずに静的に呼ばれて欲しいじゃない?)

 

そこでfinalを付けてみたら、O0でもきちんと静的に呼び出される事が分かった。

 

サンプルコード

#include <stdio.h>

 

class TestInterface {

public:

  virtual int Func(int i) = 0;

};

 

class Test : public TestInterface {

public:

  virtual int Func(int i) override /* final */ {

    // 上のfinalをつけたり、つけなかったり

    return i;

  }

};

 

int test(Test &t, int i) {

  return t.Func(i);

}

 

int main() {

  Test t;

  return test(t, 1);

}

 

これをこんな感じでコンパイルする

 

g++ -O0 -std=c++11 test.cc

ちなみに、バージョンは g++ (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4

 

んで、逆アセンブルする。

objdump -CD a.out

 

final無しの場合

000000000040067d <test(Test&, int)>:

  40067d:       55                      push   %rbp

  40067e:       48 89 e5                mov    %rsp,%rbp

  400681:       48 83 ec 10             sub    $0x10,%rsp

  400685:       48 89 7d f8             mov    %rdi,-0x8(%rbp)

  400689:       89 75 f4                mov    %esi,-0xc(%rbp)

  40068c:       48 8b 45 f8             mov    -0x8(%rbp),%rax

  400690:       48 8b 00                mov    (%rax),%rax

  400693:       48 8b 00                mov    (%rax),%rax

  400696:       8b 4d f4                mov    -0xc(%rbp),%ecx

  400699:       48 8b 55 f8             mov    -0x8(%rbp),%rdx

  40069d:       89 ce                   mov    %ecx,%esi

  40069f:       48 89 d7                mov    %rdx,%rdi

  4006a2:       ff d0                   callq  *%rax

  4006a4:       c9                      leaveq

  4006a5:       c3                      retq

 

final有りの場合

000000000040067d <test(Test&, int)>:

  40067d:       55                      push   %rbp

  40067e:       48 89 e5                mov    %rsp,%rbp

  400681:       48 83 ec 10             sub    $0x10,%rsp

  400685:       48 89 7d f8             mov    %rdi,-0x8(%rbp)

  400689:       89 75 f4                mov    %esi,-0xc(%rbp)

  40068c:       8b 55 f4                mov    -0xc(%rbp),%edx

  40068f:       48 8b 45 f8             mov    -0x8(%rbp),%rax

  400693:       89 d6                   mov    %edx,%esi

  400695:       48 89 c7                mov    %rax,%rdi

  400698:       e8 25 00 00 00          callq  4006c2 <Test::Func(int)>

  40069d:       c9                      leaveq

  40069e:       c3                      retq

 

というわけで、finalはつけような!