ページへ戻る

 印刷 

技術系備忘録​/C++​/最適化小手先テクニック​/空関数の実体はヘッダに書くべし :: シンクリッジ

xpwiki:技術系備忘録/C++/最適化小手先テクニック/空関数の実体はヘッダに書くべし

何もしない関数というのは、たまに登場してしまうと思います。
定義はあるけど、実体(.cpp)を見ると、中身がカラというケースをたまに見ます。

クラスのコンストラクタやデストラクタに多い気がしますので、まずそのあたりを例に。

  • List1
    • test.h
      class CTest
      {
      public:
      	CTest();
      	virtual ~CTest();
      	void func();
      };
    • test.cpp
      CTest::CTest()
      {}
      
      CTest::~CTest()
      {}
      
      void CTest::func()
      {
      	::AfxMessageBox( _T("func") );
      }
    • main.cpp
      #include "test.h"
      
      main()
      {
      	CTest test;
      	test.func();
      }

リスト1の CTest のコンストラクタとデストラクタは何もしてません。
普通に VisualStudio のクラスウィザードでクラスを作ると勝手に関数の雛形まで作ってくれるので、そのままになっているケースがあるのではないでしょうか。

コンパイラの最適化処理で、main() では、CTest::Func だけコールしてくれるコードになってくれれば、プログラマとしては言うことはありませんが、そうはなってくれません。

main() での処理は、
・CTest のコンストラクタをコール。
・CTest::func をコール。
・CTest のデストラクタをコール。

となり、実質不要なコンストラクタとデストラクタの関数をコールしています。(もちろんコール先では何もせずリターンします。)

コンパイラは、関数をまたぐ最適化はしてくれないと思っていたほうが良いと思います。
コンパイラは、main.cppをコンパイルしている時に、CTestのコンストラクタが空なのかどうかを判断出来ないので、実直に関数をコールするコードを出力するほかありません。

こういう場合、クラス定義(ヘッダ)の中に、関数の実体を記述すれば、余分なコールを避けることが出来ます。

  • List2
    • test.h
      class CTest
      {
      public:
      	CTest(){}			// 空関数の実体も記述
      	virtual ~CTest(){}	// 空関数の実体も記述
      	void func();
      };
    • test.cpp
      /* .cpp には実体を書かない。
      CTest::CTest()
      {}
      CTest::~CTest()
      {}
       */
      void CTest::func()
      {
      	::AfxMessageBox( _T("func") );
      }
    • main.cpp
      #include "test.h"
      main()
      {
      	CTest test;
      	test.func();
      }

リスト2のようにすることで、main() での処理は、
・CTest::func をコール。
だけになります。

しかし、最適化されないような例をあげます。
例えば、CTestのメンバ変数にクラスが含まれているような場合。(リスト3)

  • List3
    • test.h
      class CTest
      {
      public:
      	std::string m_str;
      public:
      	CTest(){}			// 空関数の実体も記述
      	virtual ~CTest(){}	// 空関数の実体も記述
      	void func();
      };

こういう場合は、m_str を初期化する必要があるので、コンパイラは、CTest のコンストラクタを端折ることはしません。

/*
ちなみに、上記例程度のメンバ変数ならばインライン処理してしまうコードが出力される模様です。メンバ変数の量やコンパイルスイッチにもよると思われます。
*/

もう一つ最適化されないケース。
virtual 関数の場合、派生先関数がコールされる場合があるので、コンパイラはその可能性がある場合は、コールを端折ることはしません。

  • List4
    • test.h
      class CTest  
      {
      public:
      	virtual void func(){}	// 空関数の実体が記述されていても
      };
    • main.cpp
      void function(CTest *p)
      {
      	p->func();			// ここではちゃんとコールする
      }

まとめ。

関数をヘッダに実装をすれば、コンパイラが勝手にインライン展開する場合がある。ということは知られていると思いますが、逆に言えば、ヘッダに実装されてない場合は(例外もありますが)インライン展開されないということです。

例えば、全ての関数のヘッダに実装するのは、コード最適化の観点から言えば、悪いことではない気がしますが、デメリットも多々ある気がします。
どの程度までの関数をヘッダに実装するかの判断は、ケースバイケースだと思いますが、こと空関数については、ヘッダに実装を書くことによるデメリットは無いと思います。
物によっては、まったく意味が無いケースもありますが、最適化される可能性を残しておく為に、空関数の実体はヘッダに記述すると吉と思います。


Last-modified: 2016-04-01 (金) 13:25:40 (JST) (2944d) by takatsuka