Raspberry Pi 3BにMainsail OSをインストール

sudo git clone https://github.com/kedei/LCD_driver
sudo chmod -R 777 LCD_driver/
cd LCD_driver/
sudo ./LCD35_hdmi

4章 分割コンパイル、ビルド

4-1 識別子とC++のキーワード

  • 識別子は、関数や型、変数といった名前に相当するもののこと
  • 概ね下記のルール
    • 大文字小文字は区別
    • 数字で始めることはできない
    • アンダースコアで始まっても良い
    • いくつか使用できないキーワードがある
  • 識別子の付け方の3例
    • スネークケース: アンダースコアで単語区切りを示す。my_test_function
    • アッパーキャメル: 各単語の最初の文字を大文字にする。MyTestFunction
    • ロワーキャメル: 最初の文字だけ小文字に、残りは各単語の最初の文字を大文字にする。myTestFunction
  • Boostや標準ライブラリではスネークケースが、Qtでは両キャメルケースが使われる
  • キーワードは変数、関数名などには使えない
    • true, if, intなど
  • 文脈依存キーワードは変数、関数名などに使えるが、好ましくない
    • final, override
  • 代替表現も変数、関数名などに使えるが、好ましくない
    • and, not, xor, orなど
  • 暗黙的に定義される識別子は下記など
識別子 内容
__FILE__ 文字列リテラル ソースファイル名
__LINE__ 数値リテラル ソースコード__LINE__が出現した行番号
__func__ 文字型の配列 関数名
__cplusplus 数値リテラル c++としてコンパイルされたときのバージョン

4-2 宣言と定義

  • 宣言とは、変数や関数、クラスなどのプログラムを構成する何かの存在を伝えるもの
return-type function-name(parameters, ...); //関数の宣言(プロトタイプ宣言)
struct struct-name;
class class-name;
union union-name;
enum class enum-name;
enum class enum-bame : underlying-type;
  • 関数を事前に宣言しておくことで、定義がプログラムの後ろのほうにあっても関数を呼び出すことができる
    • プログラムの前の方で宣言することを前方宣言とよぶ
  • 定義は関数やクタスの中身がどのようになっているかを記述すること
    • インスタンス化にはそのクラスがどのくらいのサイズのメモリを使用するのか知る必要があるが、宣言だけでは分からない
    • ポインタや参照はアドレス値のみなので、定義がなくても引数、戻り値にできる
      • ただし、定義がないとメンバにアクセスできない
  • ファイル分割を行う場合は、ヘッダファイルに宣言を、ソースファイルに定義を記述するといったテクニックが必要になる
  • クラス定義中にメンバ関数の定義を記載することもできる
    • クラス定義が長くなりやすいので、短い関数に限るべき

4-3 スコープ

  • 変数の有効な範囲をスコープ、変数が有効になっている期間のことを生存期間という
  • 関数定義の{から}までを関数スコープという
    • 関数スコープの中で定義された変数と仮引数は関数から処理が戻るときに破棄される
  • ブレースで囲めばスコープを作れる
  • グローバルスコープmain関数が呼ばれる前から終了するまで
    • global変数はこのスコープが生存期間
    • 関数の外側で定義された変数
  • static変数もプログラム終了するまで生存する
    • global変数もstaticにできるが、その場合はファイルスコープな変数になる
      • つまり、そのソースファイルの中でしか使えない
    • static変数は初期化することができるが、初期化はその宣言にたどり着いた一度目にしか行われない
    • static変数は寿命が長い以外はローカル変数と違いがない
      • つまり、宣言されたスコープでしか使用できない
    • グローバル変数は、どこで使われているか見通しが立てづらいので、static変数でもうまく記述できない場合にのみ使うべき
  • すでに破棄されてしまっている変数をさしたポインタをダングリングポインタとよぶ

4-4 初期化構文付き条件分岐

  • C++17で追加された機能
  • if文, switch文でもセミコロンを使って変数の初期化ができるようになった
    • あまり使わない
if (type_name var_name = init_value; condition)
{
 ...
}

switch (type_name var_name = init_value; condition)
{
 ...
}

4-5 分割コンパイル

  • 1ファイルの場合
    • ソースファイルを実行形式ファイルに変換する手順をコンパイルと呼ぶ
  • 複数ファイルの場合
    • ソースファイルのヘッダファイルを読み込んだり、マクロの展開などの前処理をプリプロセスとよぶ
    • プリプロセス済みソースをオブジェクトファイルに変換することをコンパイルとよぶ
    • オブジェクトファイルを結合して、実行形式ファイルに変換する手順をリンクとよぶ
    • これらをまとめてビルドとよぶ
  • externキーワードをつけると、実体を持たない変数を宣言できる
    • global変数を複数ソースやヘッダファイルで宣言すると、それぞれのオブジェクトで実体をもってしまう
    • グローバルスコープの変数でしか使えない
    • staticな変数なglobal変数はそのソース内でしか使用できないのでexternできない
  • static変数はヘッダで定義してはいけない
    • ソースファイルごとに定義が行われてしまうから
extern type-name var-name; // in global scope

3章 クラス

3-1 constメソッドについて

  • constメソッド
    • constインスタンスから呼び出される
      • 逆に、constインスタンスconstメソッド以外を呼び出すことはできない
    • メソッド内でメンバ変数を書き換えることはできない
    • mutableな変数のみ書き換えることができる
      • 使いどころが難しい
      • constインスタンスメンバ関数を書き換えるというのは直感に反する
      • メソッドの戻り値の計算に多大なコストが必要で、値をキャッシュする場合などに用いる
    • constの有無でメソッドのオーバーロードが可能
  • const変数
    • 書き換えができないメンバ変数
    • メンバ初期化子、もしくはメンバ初期化リストでのみ初期化することができる
  • https://wandbox.org/permlink/yIAc4PQpkqT6ji3p

3-2 コンストラクタ、デストラク

  • コンストラク
    • 基本的には初期化に関する実装のみを記載することが鉄則
    • コロン以降はメンバ初期化リストとよばれる
      • メンバ変数のコンストラクタを呼び出せる
  • デストラク
    • インスタンスが削除されるときに呼び出される
    • コンストラクタでメモリ確保、デストラクタでメモリの解放することをRAII (Resource Acquisition Is initializationとよぶ
    • OSが提供する特殊なリソースの返却などにも利用する

3-3 初期値を受け取るコンストラク

  • 何も定義しないとデフォルトコンストラクタがコンパイラにより作成される
    • デフォルトコンストラクタとは、引数のないコンストラクタのこと
    • 逆に、ひとつでも定義するとデフォルトコンストラクタは作成されない
  • コンストラクタ内で他のコンストラクタを呼び出す機能を移譲コンストラクとよぶ
    • 一般には、移譲先にコンストラクタの実装の大部分を任せる
    • 移譲元ではメンバ変数の初期化も行えない
  • 多くの場合はコンパイラが自動生成するコピーコンストラクタをそのまま使えば良い
  • 代入演算子と中括弧で暗黙のコンストラクタ呼び出しが行える
    • つまり、Hoge a={var1, var2};Hoge a(var1, var2);と同じ
    • しかし、Hoge a=42はパッと見てHoge a(42)と実装されているようには見えづらい
    • コンストラクタの頭にexplicitをつけることで、暗黙のコンストラクタ呼び出しを禁止することができる

3-4 デフォルトの初期値

  • クラス定義内でのメンバ変数の初期化の構文を非静的メンバ変数の初期化子(Non Static Data Member Initializer : NSDMI)と呼ぶ
    • NSDMIと呼ばれることが多い
  • 構文は下記
class A
{
    type1 var1 = defVal1;
    type2 var2 = {defVal2};
    type1 var3(defVal3);
    type1 var4{defVal4};
};
  • メンバー初期化子リストも与えられた場合はメンバー初期化リストが優先される

3-5 継承

  • ベースとなるクラスを基底クラス、基底クラスを継承したクラスを派生クラスとよぶ
    • 「クラスAから派生したクラスBが、基底クラスの機能を継承する」といいた言い回し
  • 構文は下記
class derived-class-name : access-specifier base-class-name
{
   ...
};
  • access-specifierを省略するとprivateになる
    • privateの場合、基底クラスのメンバに派生クラスからアクセスできなくなる
  • 基底クラスのメンバ関数の処理内容を派生クラスで変更することができる
    • 基底クラスのメンバ関数virtualを指定し、仮想関数にすることで、派生クラスで処理内容の変更を許可する
    • 派生クラスではoverride指定子を追加し、処理内容を書き換える
      • 派生クラスで何もoverrideしなければ、自動的に基底クラスの仮想関数を継承する
    • overrideする場合は戻り値の型、関数名、constの有無なども一致させる必要がある
    • 実は基底クラスでvirtualを指定しておけば、派生クラスのoverrideは省略できるが、わかりにくくなるので避ける
  • 派生クラスで基底クラスと同名のメンバ関数を作成すると名前の隠蔽(name hiding)が生じる
    • たとえ引数で区別したとしても基底クラスのメンバ関数を呼び出せなくなる
    • using宣言を用いることで、派生クラスでも基底クラスのメンバ関数を呼び出せるようになる
  • 派生クラスにオーバーライドを強制するメソッドを純粋仮想関数とよぶ
    • virtual method_name(args, ...) = 0;のように基底クラスで定義する
    • 派生クラスでオーバーライドしないとコンパイルエラーとなる
    • 純粋仮想関数を定義したクラスを抽象クラスとよび、インスタンスの生成ができない

3-6 オブジェクトポインター

  • 構造体と同じく、ポインタを経由してメンバにアクセスするときはアロー演算子->を使う
  • メンバ関数の仮引数とメンバ変数名が一致するとき、その変数名は仮引数として解釈される
    • メンバ変数にアクセスするときには、thisポインタを経由してアクセスする
    • thisポインタは書き換え不可
    • constメンバ関数の中ではthisポインタはconstポインタとなる

3-7 クラス、構造体、共用体の関係性

  • 構造体
    • クラスと構造体は、デフォルトのアクセス指定だけが異なる
    • クラスはprivate、構造体はpublic
    • 基底クラス(構造体)から派生するときのアクセス指定もクラスがprivateで構造体がpublic
    • 他に違いは無いが、Cからの名残で単にデータの箱をつくるときは構造体を用いることが多い
  • 共用体
    • メンバ変数はすべて同じアドレスに置かれるため、正しい意味を持つメンバ変数はどれか一つのみ
    • クラス/構造体/共用体から派生して共用体を作ることも、共用体から派生してクラス/構造体/共用体を作ることもできない
    • メンバ変数やコンストラクタなども定義できる
    • デフォルトのアクセス指定はpublic
    • メンバ関数内でthisもつかえる
  • 無名共用体
    • 共用体のインスタンスをつくるまでも無いが、複数の変数を同じアドレスに置きたい場合に利用する
    • 下の例では、クラスHogeインスタンスhogeについて、&(hoge.a) == &(hoge.b)となっている
    • 無名共用体はグローバル変数や関数内の変数としても使える
class Hoge
{
public:
    union
    {
        int a;
        float b;
    }
};

3-8 friend関数

  • メンバ関数でないにもかかわらず、privateなメンバにアクセスできるような関数を用意したい場合は、フレンド関数を使用する
    • フレンド関数はクラス宣言内でfriendをつけたものをプロトタイプ宣言する(フレンド宣言)
    • メンバ関数ではないので、引数としてクラスのポインタや参照を一つ以上受け取るのが一般的
    • 複数クラスの非公開メンバにアクセスしたい場合は、複数クラスでフレンド宣言する
    • 定義にはfriendは不要
  • フレンド宣言はクラスの外側では行えない

3-9 staticクラスメンバ

class A
{
public:
    static int stVal;
}
;

int A::stVal = 3;

C++ 内容整理

背景

3年間機械系エンジニアとしてCAEに取り組んでいたが、一念発起してソフトウェアエンジニアに転身。 数年ぶりにC++を使い始めるも、業プロと趣味(?)プロのギャップに色々と苦労している。 入門書の内容を整理しながら内容を整理していく。

参考にする入門書