[ C++で開発 ]

自分用コーディング標準(C++)

C++コーディング標準はいくつかありますが、どうもピンとくるのがないので良いとこ取りで自分用コーディング標準をまとめます。

スタイル編

ファイル構成

C++は、マルチパラダイム言語です。オブジェクト指向プログラミングを中心に据える場合はクラスが構成要素の中核となりますが、ジェネリックス(テンプレート)プログラミングを中心に据える場合はテンプレート集が構成要素の中核となります。もちろん構造化プログラミングを中心に据える場合はフリー関数が構成要素の中核となります。

クラス中心方式を採用する場合

主にオブジェクト指向プログラミングを行う場合のファイル構成法です。

ジェネリックス中心方式を採用する場合

ヘッダーファイル

クラス定義のレイアウト

クラス定義のレイアウトにはいくつか流儀があります。可視性制御順(public/protected/private)に並べるもの、意味順に並べるもの(構築系、アクセッサ系、ミューテータ系)、などです。ここでは、後者の意味順に並べる方法のうち、「なるほど、これはよい方法だ」とうなった記述方法を挙げます。

class Sample {
// コンストラクタ
public:
    Sample();
    Sample(Sample& r);
protected:
    Sample(ResourceImpl* rimpl);
// デストラクタ
public:
    virtual ~Sample();
// 操作
public:
    void open();
    void open(ResourceImpl* rimpl);
    void close();
// 属性
public:
    size_t getSize() const;
protected:
    void setSize(size_t const size);
...
// 非実装提供
private:
    Sample& operator=(Sample& rhs);
};

 もし、よくあるpublic、protected、privateの順に定義するやり方でレイアウトしていたら、publicなメソッドをprivateに変更すると、定義行の位置が大きく変わり、バージョン管理システムでの差分が見えなくなるという問題がある。

ソースファイル

インデント

命名編

関数、メンバ関数

ローカル変数

ユーザー定義型(class、enum、struct)

※ 標準I/OストリームやSTLの命名とは異なる

名前空間

マクロ定義

変数名

グローバル変数

データ・メンバ(メンバ変数)

テンプレート引数のtypename

関数の引数

定数

ハンガリアン記法は使わない

命名規則一覧表

命名項目 お薦め規則
名前空間 すべて小文字。単語間連結は直接。
ユーザ定義型
class, struct, enum, typedef
アッパーキャメルケース
メンバ関数名 インスタンス ローワキャメルケース
クラス(static)
メンバ変数名 インスタンス ローワキャメルケースで末尾にアンダースコアを付加
クラス(static) ローワキャメルケースで末尾にアンダースコア2連を付加
引数名 ローワキャメルケース
ローカル変数名 ローワキャメルケース
グローバル関数名
グローバル変数名
ファイルスコープ関数名 ローワキャメルケース
ファイルスコープ変数名 ローワキャメルケース
マクロ定義名 すべて大文字で単語間連結はアンダースコアを用いる
アッパーキャメルケース
先頭は大文字、複数単語で構成するときは続く単語の先頭1文字を大文字として直接連結。
例: AbstractButton
ローワキャメルケース
先頭は小文字、複数単語で構成するときは続く単語の先頭1文字を大文字として直接連結。
例: createPushButton、currentPosition

コメント編

ファイルコメント

コード内のコメント

ガイドライン編

クラスの定義

クラス定義の雛型

class Simple {
    int value;
    explicit Simple(const Simple& rhs);
    void operator=(const Simple& rhs);
public:
    Simple();
    ~Simple();
};

コピーコンストラクタと代入演算子はコンパイラの自動生成が適用されないように明示的に定義する。ただし、クラスの設計上コピーコンストラクタと代入演算子を提供すると決定するまではprivateとしておく。

引数が1つのコンストラクタは、explicit宣言して暗黙的に変換されるのを抑制する。

Simple::Simple() : value(0) {
}

Simple::Simple(const Simple& rhs) {
    value = rhs.value;
}

Simple::~Simple() {
}

void Simple::operator=(const Simple& rhs) {
    if (this == &rhs) {
        return;
    }
    value = rhs.value;
}

定数・値の定義

定数は、使用される範囲(スコープ)と用途に応じて定義する

定数は、定数が使用される範囲のもっとも小さい(局所的な)スコープに定義する。

定数の使用注意事項
#defineで定数を定義して使用しない。defineマクロは、型安全性とnamespace等のスコープ管理を破壊するため。
classの静的メンバ変数のconstによる定数定義を安易に使用しない。protected/privateな場合はクラス静的メンバ変数にはしない。publicな場合は、そのクラスが提供するのがふさわしい定数に限って使用する。

以下に、定数を使用する局面とその場合の定数定義方法を規定する。

  1. 定数が一箇所でしか使用されない(唯一の使用)
    定数を定義せず、即値で使用する。
  2. 定数が1つの(メンバ)関数内の複数個所で使用される
    定数を、関数内にconst staticローカル変数として定義する
    // Foo.cpp
    bool isInclude(double x, double y) {
        const static double ACCELERATION_DUE_TO_GRAVITY = 9.80665;
        ...
    }
  3. 定数が同一ファイル(.cpp)内の複数個所で使用される
    定数を実装ファイル(.cpp)の無名namespace内にconst変数として定義する
    // Foo.cpp
    namespace {
    const double ACCELERATION_DUE_TO_GRAVITY = 9.80665;
    }
    
    bool updatePosition(double x, double y, double tick) {
        double newX = x + ACCELERATION_DUE_TO_GRAVITY * tick;
        ...
    } 
  4. 定数が同一ファイル(.cpp)内の関数の引数・戻り値として使用される
    定数以外の値が引数・戻り値として使用されることを防止する必要があるならば、enumを使用する
    // Foo.cpp
    namespace {
    enum Suit {
        CLUBS, DIAMONDS, HEARTS, SPADES
    };
    }
    
    bool canAccept(const Suit& card) {
        if (DIAMONDS == card) {
            return true;
        }
        return false;
    }
    注記1)
    enum型変数へint型の値を代入しようとしてもC++ではコンパイルエラーとなりますが、enum型列挙子をint型変数へ代入することはできます。
    注記2)
    enum型列挙子は、enum型宣言に閉じられずに外側のスコープに公開されます。上のコード例では、ファイル全体にCLUBS, DIAMONDS, ... の識別子が適用されます。ファイル内ならまだよいですが、ヘッダーファイルに定義し複数ファイルに利用される場合、注意が必要です。
  5. ファイル(.cpp)をまたがって定数が使用される
    定数を提供する側で用意したヘッダーファイルに、const変数で定義します。このとき、必ず名前付きnamespaceの中にenum型を定義する。あるいはクラス(オブジェクト指向)設計を適用している場合、クラス内にenumを定義する。
    // Foo.h
    namespace geophysics {
    const double ACCELERATION_DUE_TO_GRAVITY = 9.80665;
    }
    
    // Bar.cpp
       ...
       double val = geophysics::ACCELERATION_DUE_TO_GRAVITY * weight;
    注記) グローバル(namespaceスコープ)のconst変数を#includeしてコンパイルされるコードを見ると、コンパイル単位でローカルな領域に定数の値が格納され、定数のシンボルは公開されないようです。(GCC/C++ 4.1.2 on Linuxで確認)
  6. ファイル(.cpp)をまたがって関数の引数・戻り値に使用される
    定数を提供する側で用意したヘッダーファイルに、enum型const変数として定義します。このとき、必ず名前付きnamespaceの中にenum型を定義する。あるいはクラス(オブジェクト指向)設計を適用している場合、クラス内にenumを定義する。
    例1) namespace内にenum型を定義したサンプルコード
    // Foo.h
    namespace cards {
    enum Suit {
         CLUBS, DIAMONDS, HEARTS, SPADES
    };
    }
    
    // Bar.cpp
       ...
       if (card.suite() == cards::SPADES) {
           ...
       } 
    例2) クラス内にenum型を定義したサンプルコード
    // Bar.h
    class Cards {
    public:
        enum Suit {
            CLUBS, DIAMONDS, HEARTS, SPADES
        };
        ...
    };
    
    // Hoge.cpp
        ...
        if (card.suite() == Cards::HEARTS) {
            ...
        }
  7. 定数値と定数名称をペアで管理したい(コードと名称)
    T.B.D.
  8. 集合として列挙を扱いたい
    T.B.D.
  9. 再コンパイルすることなく定数値の変更を反映したい
    T.B.D.

ヌルポインタ

例外

値をスローし参照でキャッチ

class Exception {};

void throwFunction {
    throw Exception();
}

void doSomething() {
    try {
        throwFunction();
    } catch (const Exception& ex) {
        ...
    }
}