分割コンパイルと静的ライブラリでチェーン実装のメモ（c++）

## 注意事項

+ 2週間くらい1人で適当に弄った結果，アテにならん
+ 静的リンクだけやってる
    + 動的リンクはやらない
    + 動的ロードもやらない
    + あくまで過酷環境の組込実装のためにやってる
        + 動的リンクも動的ロードも無理らしい…
        + 暇があれば後でやってみようと思ってる
+ スマートポインタは使わない
    + `c++98`を想定してる
    + スマートポインタは`c++11`以降の機能なので




# 概要

+ プロジェクトの構成
    + 認識のしやすさ
    + 管理のしやすさ
    + モジュールとして分割して開発
+ `makefile`の改造
    + シェルの文法なのか`make`の文法なのかこれもうわかんねぇな
    + `make`の機能だけで色々やろうとしたが個人的には無理だった
        + 文法の縛りがキツくて関数の実用性をあまり感じなかった
    + もうシェルスクリプトを叩く方が早い？初期化もシェルで叩く？
        + モジュール追加時の自動構成もできると気持ちが良さそう
+ クラス`x`をメンバ変数に持つクラス`y`を定義するヘッダファイルでは，前方宣言で`x`を定義して`include`の多重ロードを防止する
    + つまりヘッダファイルでは`x`を定義して`include`せずに，ソースファイルでは`x`を定義するヘッダファイルを`include`する
    + たとえば「「「クラス`x`」を抱えるクラス`y`」を抱えるクラス`z`」を定義する時にヘッダファイルに依存関係を全て表示すると，`y`を定義するヘッダファイルに加えて`y`を経由して`x`を定義するヘッダファイルまでロードする，といった多重インクルードが発生する
        + 何レイヤも下にあるライブラリのヘッダファイルを遡上するのは，まともにやろうとしても`include path`の管理が壊れる
        + 名前空間の管理も高レベルになる，ヘッダファイルの挙動によっては思わぬ動作を生むかもしれない，俺は辛い耐えられない
        + 1つのライブラリを複数のソースで構成して`include`するならOK，ただしそれでも`include path`には十分に注意する
+ そのような実装をするクラス`y`はクラス`x`をメンバ変数に持つが，コンパイラの都合によって実体ではなくポインタで持つ必要があり
    + たとえば「「「クラス`x`」を抱えるクラス`y`」を抱えるクラス`z`」の実装をコンパイルする際は，多重インクルードが阻止されているために`x`のサイズが不定となり，つまり`y`のサイズも不定となり，そして`x`のサイズも不定となるため，普通にエラーを吐かれる
        + もっと簡単に表現すると**2つ下のレイヤで定義されたクラスのサイズがわからないから実体を持つとコンパイルできない**
    + 実体を持たずにポインタを持つならば，ポインタのサイズは処理系によれどいずれも既知となるはずなので，この問題は起こらない
        + ある意味でシンボル指向に基づくオブジェクト指向みたいな感覚を覚えた，普段からリンクする時のことを考えた方が良い
    + **ユニークポインタが未実装の`c++98`では`new`と`delete`を自力で管理しないとメモリリークする**ので，気をつけるべし
    + さらに言えばヘッダファイルが隔離されているせいで**2つ下レイヤのクラスにあるメソッドや変数は叩けない**，気をつけるべし
+ 静的ライブラリの取り扱いについて
    + 実行ファイルの生成時にシンボルの多重定義を防止するには，ライブラリ毎に生成した静的ライブラリを最後にまとめて呼び出す（普通はこうするっぽい）か，依存する静的ライブラルをバラして得たオブジェクトとコンパイルで得たオブジェクトをまとめてマージして重複を消し飛ばす作業を，ライブラリ毎の静的ライブラリの生成からメインのオブジェクトが呼ぶ静的ライブラリの生成まで一貫してやり続けるか…
        + 前者はライブラリの依存関係がソースファイルの`include`の依存関係に一致しないので．リンカに渡すパスの管理が面倒
        + 後者はディスクを無駄に消費する，ライブラリ毎に設定されるべき責任境界が壊れる，実装の変更の反映も簡単にできない
    + ちなみに過酷環境の組込実装では浮動小数点演算が`run-time-support`のライブラリとして提供されている場合があるらしい




# プロジェクトの構成
以下は一例

```zsh
.
├── bin
│   └── main
├── libA
│   ├── inc
│   │   └── libA.hpp
│   ├── lib
│   │   └── liblibA.a
│   └── src
│       ├── clsA.cpp
│       └── clsA.hpp
├── libB
│   ├── inc
│   │   └── libB.hpp
│   ├── lib
│   │   └── liblibB.a
│   └── src
│       ├── clsB.cpp
│       └── clsB.hpp
├── makefile
└── src
    └── main.cpp
```


## 規則

1. モジュール毎にディレクトリを設置
1. その下は`inc`と`lib`と`src`に分割
1. `src`にはソースファイルとヘッダファイルを設置
1. `lib`には静的ライブラリを設置
1. `inc`のは`src`にあるヘッダファイルを全て`include`した代表ヘッダファイルを設置
    + 他モジュールやメインモジュールから参照されるヘッダファイルはこの1つに統一する
    + モジュールのコンパイル時にソースファイルに応じて`makefile`で自動的に生成する
    + ただし`include path`は`src`の方にも通す，代表ヘッダファイルが呼び出すので




# `makefile`の改造

以下は一例

```makefile:makefile
SHELL		=	/bin/zsh

MAIN_SRC	=	src/main.cpp
MAIN_OBJ	=	main.o
MAIN_BIN	=	bin/main

GCC_BIN		=	clang++
GCC_FLG		=	-std=c++98 -O2


make	:
	make libA
	make libB
	make main


libA	:	FORCE
	$(call setAllVar,libA)
	$(GCC_BIN) $(GCC_FLG) -c $(libA_SRC)
	@ar cr $(libA_OBJ) *.o
	@rm -f *.o
	$(shell echo '#pragma once' > $(libA_REP);)
	$(foreach hed,$(libA_HED),$(shell echo '#include "$(hed)"' >> $(libA_REP);))

libB	:	FORCE
	$(call setAllVar,libB)
	$(call setRefVar,libA)
	$(GCC_BIN) $(GCC_FLG) -c $(libA_INC) $(libB_SRC)
	@ar x $(libA_OBJ)
	@ar cr $(libB_OBJ) *.o
	@rm -f *.o __.*
	$(shell echo '#pragma once' > $(libB_REP);)
	$(foreach hed,$(libB_HED),$(shell echo '#include "$(hed)"' >> $(libB_REP);))

main	:	FORCE
	$(call setRefVar,libB)
	$(GCC_BIN) $(GCC_FLG) -c $(libB_INC) $(MAIN_SRC)
	@ar x $(libB_OBJ)
	$(GCC_BIN) $(GCC_FLG) -o $(MAIN_BIN) $(MAIN_OBJ) $(libB_LIB)
	@rm -f *.o __.*


FORCE	:


define setAllVar
	$(eval $1_HED = $(notdir $(wildcard $1/src/*.hpp)))
	$(eval $1_SRC = $(wildcard $1/src/*.cpp))
	$(eval $1_OBJ = $1/lib/lib$1.a)
	$(eval $1_REP = $1/inc/$1.hpp)
	$(eval $1_INC = -I$1/inc -I$1/src)
	$(eval $1_LIB = -L$1/lib -l$1)
endef

define setRefVar
	$(eval $1_OBJ = $1/lib/lib$1.a)
	$(eval $1_REF = $1/inc/$1.hpp)
	$(eval $1_INC = -I$1/inc -I$1/src)
	$(eval $1_LIB = -L$1/lib -l$1)
endef
```


## 説明

+ `shell`は`zsh`を指定する
    + シェルスクリプトが必要な場合はやっぱりzshで書きたい
+ ただしシェルスクリプトの中で`make`の機能を叩くのは無理
    + 関数を書いても実用性を感じられなかったのはこのため
    + せいぜい変数を自動設定するくらいしかできなさそう…
+ 存在するソースに応じ代表ヘッダファイルを自動的に生成する
    + `$(foreach)`して`$(shell echo)`したらできた…
+ 空変数`FORCE`を使って常に強制的に`make`を実行させる
    + モジュール毎にコンパイルするだけなら十分だと思った
+ `-L`は静的ライブラリの探索パスでリンク時に使う，`-I`はヘッダファイルの探索パスでコンパイル時に使う
+ リンク時に`-L`と一緒に使う`-l`はライブラリを指定するが`(lib)libA.a`みたいに先頭の`lib`が削れるらしい


## 静的ライブラリの取り扱い

バラしてマージするパターンを実装した
`ar x libName`でバラして`ar cr libName *.o`でマージして`rm *.o __.*`で一時ファイルを消す

macOSに入る`command line tool`の`clang++`は`__.SYMDEF`なるものを出すが，たぶん`gnu`の`clang++`でも同じことが起きる
見た感じシンボルのリスト（が短縮されたもの）が入ってるテキストファイルっぽかったが，用途がよくわからない，調べる必要があるかも




# 多重ロード防止実装

以下は一例


## `libA`

```cpp:clsA.hpp
#pragma once

namespace libA{

class clsA{
    private:
        int __Num;
    protected:
        int _Num;
    public:
        clsA();
        ~clsA();
        int Num;
        void aLog();
};

}
```

+ 3レベルそれぞれのアクセス権限に1つずつ変数を置く

```cpp:clsA.cpp
#include <iostream>
#include "clsA.hpp"

using namespace std;
using namespace libA;

clsA::clsA(){
    cout << "construct A" << endl;
    __Num = 0;
    _Num = 1;
    Num = 2;
}

clsA::~clsA(){
	cout << "destruct A" << endl;
}

void clsA::aLog(){
    cout << "  log from A" << endl;
    cout << "    private num: " << __Num << endl;
    cout << "    protected num:  " << _Num << endl;
    cout << "    public num: " << Num << endl;
}
```

+ コンストラクタとデストラクタを追跡する
+ 同一オブジェクト内メソッドであれば，どのアクセスは権限レベルでも問題はない


## `libB`

```cpp:clsB.hpp
#pragma once
namespace libA{
    class clsA;
}

namespace libB{

class clsB{
    private:
        libA::clsA* __aObj;
    protected:
        libA::clsA* _aObj;
    public:
        libA::clsA* aObj;
        clsB();
        ~clsB();
        void bLog();
};

}
```

+ 1つ下のレイヤのクラスをポインタで持つ
    + 3レベルそれぞれのアクセス権限に1つずつ置く

```cpp:clsB.cpp
#include <iostream>
#include "libA.hpp"
#include "clsB.hpp"

using namespace std;
using namespace libA;
using namespace libB;

clsB::clsB(){
    cout << "construct B" << endl;
    __aObj = new clsA();
    _aObj = new clsA();
    aObj = new clsA();
}

clsB::~clsB(){
    cout << "destruct B" << endl;
    delete __aObj;
    delete _aObj;
    delete aObj;
}

void clsB::bLog(){
    cout << "log from B" << endl;
    cout << "  private A:" << endl;
    __aObj->aLog();
    cout << "  protected A:" << endl;
    _aObj->aLog();
    cout << "  public A:" << endl;
    aObj->aLog();
}
```

+ `new`と`delete`でメモリを管理する
    + `c++11`以降はユニークポインタを使うべき？
+ ポインタで持つので`.`ではなく`->`で変数やメソッドを呼び出す


## `main`

```cpp:main.cpp
#include <iostream>
#include "libB.hpp"

using namespace std;
using namespace libB;

int main(){
    clsB* bObj = new clsB();;
    bObj->bLog();
    delete bObj;
};
```

+ クラス設計するわけでもないので，オブジェクトは直置きできるはず
    + が，`new`と`delete`を使った方がコーディング規約的に楽かも
+ `main`からは2つ下のレイヤの`clsA`の変数とメソッドを叩けない
    + ある意味では意図せずにセキュアな実装になっててビックリした


## 実行

```zsh
% make    
make libA
clang++ -std=c++98 -O2 -c libA/src/clsA.cpp
make libB
clang++ -std=c++98 -O2 -c -IlibA/inc -IlibA/src libB/src/clsB.cpp
make main
clang++ -std=c++98 -O2 -c -IlibB/inc -IlibB/src src/main.cpp
clang++ -std=c++98 -O2 -o bin/main main.o -LlibB/lib -llibB
% bin/main
construct B
construct A
construct A
construct A
log from B
  private A:
  log from A
    private num: 0
    protected num:  1
    public num: 2
  protected A:
  log from A
    private num: 0
    protected num:  1
    public num: 2
  public A:
  log from A
    private num: 0
    protected num:  1
    public num: 2
destruct B
destruct A
destruct A
destruct A
```




# 大域変数に自作クラスを使うと…

過酷環境の組込実装では初期化と割込制御を`int main(){}`と`interrupt void xxx(){}`のように実装するので，例えば制御器は大域変数を介して動作する必要がある
それぞれの関数や変数は別のソースに書いて`extern`してまとめてコンパイルすれば使えるが，そういうやり方ではいつか地獄を見るのでよろしくないとされる

では大域変数として制御器の機能を持つ自作クラスをオブジェクト化するとどうなるのかと言えば，（処理系によって違うが）`main`の実行前の処理にオブジェクトのコンストラクタが走るように登録されたり，実行後の処理にデストラクタが走るように登録されたりする，これも`run time support`のライブラリで提供される機能となる
しかし実質的に`main`が初期化ルーチンとなっているなら，クラスのポインタを大域変数に置きつつ後で`new`で実体化しても良さそうなのだが，すると`delete`ってどうやって登録すれば良いのだろうか…？