インライン関数

■概要

インライン関数とは処理の高速化で使用される手法の一つです。
通常の関数の呼び出しはアドレスに登録されている関数のポインタを使用して
その関数先に移動し、処理を実行しますがインラインでは移動をせずに
呼び出されたソースに関数がコンパイル時に展開されます。

●書式
	書式例:
		inline 戻り値の型 関数名(引数)
		{
			処理内容
		}

	具体例:
		inline int AddSum(int a, int b)
		{
			retrun (a + b);
		}

●通常関数とインライン関数のコンパイル時のソース内容
	通常関数はコンパイル時でも通常通りincludeファイルが展開され
	objファイルが作成されますが、インライン関数の場合
	インライン関数を使用している箇所が関数ではなく、
	その関数で定義している内容に置き換えられます。
	関数呼び出し部分がインライン関数の内容に置換されることを
	インライン展開と呼びます。

	・通常
		ソース:
			Test.h
			1	#ifndef TEST_H_
			2	#define TEST_H_
			3	int AddSum(int a, int b)
			4	{
			5		return (a + b);
			6	}
			7	#endif

			Main.cpp
			1	#include "Test.h"
			2
			3	void main(void)
			4	{
			5		int a = AddSum(1, 3);
			6	}

		コンパイル後:
			Main.cpp
			1	#ifndef TEST_H_
			2	#define TEST_H_
			3	int AddSum(int a, int b)
			4	{
			5		return (a + b);
			6	}
			7	#endif
			8
			9	void main(void)
			10	{
			11		int a = AddSum(1, 3);
			12	}
	
	・インライン
		ソース:
			InlineTest.h
			1	#ifndef INLINE_TEST_H_
			2	#define INLINE_TEST_H_
			3	inline int AddSum(int a, int b)
			4	{
			5		return (a + b);
			6	}
			7	#endif

			Main.cpp
			1	#include "InlineTest.h"
			2
			3	void main(void)
			4	{
			5		int a = AddSum(1, 3);
			6	}

		コンパイル後:
			Main.cpp
			1	#ifndef INLINE_TEST_H_
			2	#define INLINE_TEST_H_
			3	inline int AddSum(int a, int b)
			4	{
			5		return (a + b);
			6	}
			7	#endif
			8
			9	void main(void)
			10	{
			11		int a = return (a + b);
			12	}

●インラインの注意
	・展開されないケースがある
		インライン関数は関数の規模が大きい判断された場合は
		インライン展開されず、通常の関数呼び出しと同様に扱われます。
		なので、インライン関数の処理は小規模にしないといけません。

	・ヘッダファイルでコーディング
		インライン関数は「宣言 = 定義」とする必要がありますので、
		様々なファイルで使用するためにヘッダファイルでコーディングします。

●メリット
	・高速化
		インライン関数のメリットは高速化を見込めるという点です。
		関数呼び出しの負荷がなくなり、そのソース内で処理が
		展開、実行されるので高速化が見込めます。

		インライン関数が活きる例:
			forやwhileなどのループ処理内での関数呼び出しはそのループ回数分
			関数が呼び出されることになります。
			なので、その関数呼び出しをインライン関数にした場合、
			ループ回数が多ければ多いほど高速化につながります。

			サンプルコード:
				#include <iostream>
				#include <Windows.h>
				#include <stdio.h>

				#define USE_INLINE

				#ifdef USE_INLINE

				inline unsigned int Pow(int x1, int x2)
				{
					return (x1 * x2);
				}
				#else

				unsigned int Pow(int x1, int x2)
				{
					return (x1 * x2);
				}

				#endif

				void main(void)
				{
					LARGE_INTEGER f;
					if (!QueryPerformanceFrequency(&f))
					{
						return;
					}

					LARGE_INTEGER s, e;

					QueryPerformanceCounter(&s);

					unsigned int pow = 0;
					for (int i = 0; i < 10000; i++)
					{
						for (int j = 0; j < 10000; j++)
						{
							pow = Pow(i, j);
						}
					}

					printf("pow%d\n", pow);
					QueryPerformanceCounter(&e);
					double t = (double)(e.QuadPart - s.QuadPart) / 
										f.QuadPart;
					printf("time = %fsec\n", t);
				}

				インライン展開なし:
					0.40806sec

				インライン展開有り:
					0.24090sec
	
●デメリット
	・ファイルサイズの増大
		インライン関数は関数呼び出しではなく、
		ソースファイルに関数が展開されることになるので、
		インライン関数の呼び出しが多いほどファイルサイズが増大します。
		ファイルサイズ増加に伴いビルドにかかる時間も増えてしまいます。

	・隠蔽性の低下
		クラスに直接コードを書くことになるので、
		コードの隠蔽性が低下します。

	・ビルドファイルの増加
		インライン関数を修正することで、そのヘッダをインクルードしている
		全てのcppファイルに対して再度ビルドの必要が発生するので、
		頻繁にインライン関数を変更していると、
		毎回複数のファイルをビルドすることになり、
		その分だけ時間がかかります。

●クラス内でのメンバ関数の定義
	C++では以下のようにクラス内で関数を定義することが可能です。

	例:
		class Test
		{
		public:
			int AddSum(int a, int b)
			{
				return (a + b);
			}
		};

	このようにクラスの中でメンバ関数を定義した場合
	自動的にインライン関数として扱われます。
	もちろん、関数の規模が大きい場合は展開はされません。
	なので、ゲッターやセッターなどのコード量が少ない関数は
	クラス内に記述することが多いです。

■インライン展開の確認方法

VisualStudio2013
	1.上段メニュー => デバッグ => プロパティ => 構成プロパティ =>
	  C/C++ => 全般 => デバッグ情報の形式を「Zi」にする

	2.上段メニュー => デバッグ => プロパティ => 構成プロパティ =>
	  C/C++ => 最適化 => 最適化を「カスタム」にする

	3.上段メニュー => デバッグ => プロパティ => 構成プロパティ =>
	  C/C++ => 最適化 => インライン関数の展開を「拡張可能な関数すべて」
	  「__inlineのみ」にする

	4.展開を確認したい関数の呼び出しでブレークポイントを設定して実行する

	5.ブレークポイントが止まったら上段メニュー => デバッグ => ウィンドウ =>
	  逆アセンブルをクリック

	以下はインライン展開されたアセンブラと展開されていないアセンブラの内容です。
			
		関数内容:
			inline int AddSum(int a, int b)
			{
				return (a + b);
			}

		インライン展開されていないアセンブラ
			int a = AddSum(1, 1);
			01071037  push        1  
			01071039  push        1  
			0107103B  call        AddSum (0107100Ah)  <= 関数呼び出し

		インライン展開されているアセンブラ
			int a = AddSum(1, 1);
			01391027  mov         eax,1  
			0139102C  add         eax,1  
			0139102F  mov         dword ptr [a],eax  <= 展開された処理

■inline宣言の調査結果

以下は通常の関数やクラスのメンバ変数に対してinline宣言を行った結果です。

前提
	関数定義のcpp、またはhと呼び出しのcppは別
		
	例:
		関数定義:test.cpp
		呼び出し:main.cpp

クラス:
	クラス内関数定義(.h)
		inline設定 => インライン展開
		inline非設定 => インライン展開

	クラス外関数宣言(.cpp)
		関数宣言のみinline設定 => エラー
		定義にinline設定 => エラー
		関数宣言、定義にinline設定 => エラー

通常関数:
	宣言 => .h、定義 => .cpp
		関数宣言のみinline設定 => エラー
		定義のみinline設定 => エラー
		関数宣言、定義ともにinline設定 => エラー

	宣言、定義 => .h
		関数定義にinline設置 => 展開

	※関数の呼び出し側と定義が同じcppならエラーにはならない
	 この場合、関数宣言と定義のどちらにinlineをつけても展開される

	例:
		Test.cpp
		/*
			宣言と定義どちらに付けても動作する
		*/
		inline int AddSum(int a, int b);

		inline int AddSum(int a, int b)
		{
			return (a + b);
		}

		void main(void)
		{
			int sum = AddSum(1, 3);
		}

検証の結果inline宣言は関数の宣言と定義を別にした関数で使用すると
基本的にエラーが発生するようです。
インライン関数を使用する場合はヘッダファイルにコーディングしましょう。