C++ コーディングスタイルメモ

  • 110
    Like
  • 2
    Comment
More than 1 year has passed since last update.

はじめに

自分なりのコーディング規約をまとめておき,見返すときに使用する.順番など意識せず適当に書いているのでまとまり感はない.

参考文献

以下を参考に作成いたしました.書籍類は,中級者向けかも.少なくとも初心者向けではないと思います.

  • 書籍
    • リーダブルコード: 読みやすいコードとは何か? 伝わりやすいコードとは何かをまとめた良書.O'Reillyamazon
    • Effective C++: 古いと言われることもあるが,C++ のデファクトスタンダードともいわれる良書.いろいろなテクニックや考え方が載っている. amazon
    • C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス:Effective C++ と似たような面が多いがやっぱり参考になる.
    • CODE COMPLETE [上・下]:設計から実装まで幅広い範囲で様々な考え方が記載されている.完読していない.
  • Google コーディング規約 : Google が規約しているコーディングスタイルなので参考になる部分が多い.特に賛成と反対の意見の双方が述べられているため説得力が強く採用するか不採用にするかの基準になる.http://www.textdrop.net/google-styleguide-ja/cppguide.xml
  • C++コーディング規約 : http://robolin.jp/wiki/cpp-style-guide

これら参考文献が明確にある場合,[Google],[Effective C++] など明示しようと思います.

ファイル名 [Google]

以下のように,全て小文字でアンダースコアでつなげる.C++ であることを強調するため,*.cpp*.hpp を用いる.

ファイル種 拡張子
ソースファイル file_name.cpp
ヘッダファイル header_name.hpp

*.hpp, *.cpp は,windows系, *.cc, *.hh は,posix系 と聞いたことがある(本当?).

ファイル名はわかりやすく具体的な名前であること.クラスを定義しているときはクラス名に合わせること.
たとえば,StringPrinter というクラス名の時は,string_printer.hppstring_printer.cpp とする.

警告レベルを最高にする

コンパイル時の警告レベルは必ず最大に警告ゼロでコンパイルを終えるようにすること.警告は不具合の原因となり得る事柄を教えてくれる.警告,単に無くすためのコードを書くことは良くないとされる.警告を正しく理解し解消に取り組むこと.[C++Code]

void foo(int value)
{
    std::cout << "値の表示:" << std::endl;    // value を使ってない.警告でそのことを教えてくれる.
}

インデント

インデントはスペース 4 文字とする.

タブ,スペースのあつかい

必ず,半角スペース4つで統一すること.タブはエディタや環境によって幅が異なる場合がある.[Google]

int main()
{
    int foo = 0;    // ← NG.タブは,エディタによって空白数が異なるため
    int bar = 0;    // ← OK.半角文字 4つは,エディタによらず同じ空白数となるため
}

ヘッダファイルについて

禁止事項

ヘッダファイル内に using namepsaceusing を記述することを禁止する.ヘッダにそれらを記述するとヘッダ読み込んだソースにまで影響され,思わぬ障害が出る.ヘッダに using namespace を記述すると名前空間の意味が薄れる.

#include <~> は,その場でインクルードされたファイルが展開されるにすぎないことを意識する.

namespace my_project {
#include <string>     // 文法的には正しい.でも ??? どういう動作になる ???
}

ヘッダ依存をできるだけ減らす

ヘッダ依存が多いとちょっとしたヘッダの変更でコンパイルが長くなる.そのため,前方宣言で済む場合は全て前方宣言ですます.

かといて Pimplイディオム を使いすぎてはダメ.プロジェクトの人間にちゃんと伝わるか考える.

Pimpl イディオム

Pimpl イディオムは,クラス内クラスを前方宣言し,ポインタ(スマートポインタ)とすることで実装をヘッダファイルに一切書かないというもの.隠蔽できさらにヘッダの依存を減らせる効果がある.しかし,そこまでする必要があるのか考えよう.

/*----- a.hpp -----*/
#ifndef A_HPP
#define A_HPP
#include <string>

// Foo の前方宣言
class Foo; 

class A 
{
    std::string name() const;

private:
    class Impl;

     // 実体を隠す.こうすれば Impl が定義されたヘッダをinclude する必要が無く,private メソッドも隠蔽かできる.
    std::shared_ptr<Impl> impl_;    

    // 実体を隠す.こうすれば Foo が定義されたヘッダをinclude する必要が無い
    std::shared_ptr<Foo> class_;     
};
#endif // #ifndef A_HPP

/*----- a.cpp -----*/
#include <a.hpp>

//
// class A inner Class Impl 
//
class A::Impl
{
    Impl();

    std::string name() const
    {
        return name;
    }

    void setName(const std::string &name)
    {
        name_ = name;
    }

private:
    std::string name_;
};


//
// class A
//
A::A() : impl_(std::shared_ptr<Impl>())
{
}

std::string A::name() const
{
    return impl_->name();
}

void A::setName(const std::string &name)
{
    impl_->setName(name);
}

インクルードの順序

インクルード順は,google に準じる.[Google]

  1. 自身のソース対するヘッダ
  2. Cシステムファイル
  3. C++システムファイル
  4. その他ライブラリの .h ファイル (boost / opencv など)
  5. プロジェクトの .h ファイル

命名規則

命名規則の種類

一般的には,以下の4種類がよく使われる.

命名規則名 日本語名 内容
PascalCase キャメルケース 先頭大文字,それ以降の単語区切りも大文字
camelCase パスカルケース 先頭小文字,それ以降の単語区切りは大文字
snake_case スネークケース すべて小文字,単語区切りをアンダースコア _ でつなぐ
SNAKE_CASE 知らない すべて大文字,単語区切りをアンダースコア _ でつなぐ

命名規則の適応例

ここでは,以下の通りとする.他のプロジェクトでは別の使われ方がされる.

命名規則名 用途
PascalCase クラス名
camelCase メソッド名,関数名,変数名.
snake_case ファイル名.名前空間.std のメソッド名,変数名,型名に準じる場合
SNAKE_CASE マクロ名,static const 定数,列挙体

SNAKE_CASE は,本当はマクロ名だけにするのが最も良いかも.C++ では,マクロを使うときは相当の慎重さが求められるためである.SNAKE_CASE をマクロ名とすることにより,マクロであることを強調し注意を促すことができる.定数と列挙の体の代替としては,PascalCase がふさわしいと思われる.

名前と名前空間以外で snake_case を用いる場合は,自前の verctor 風のクラスを作成したとき,もしくは,std コンテナのラッパを用いたときに使用する.たとえは,以下の様なクラスのときに使う.

/**
 * 従業員クラス
 *
 * 住所,住所など保持する
 */
class Eemployee
{
public:
    std::string name;       //!< 氏名
    std::string address;    //!< 住所
};


/**
 * 従業員リスト
 *
 * 複数の従業員を管理する.
 * 直接 std::vector<Eemployee> とせす,
 * 内部でラッパすることでデータ型を隠す.
 */
class EemployeeList
{
private:
    typedef std::vector<Eemployee> EemployeeList;

public:
    /*
     * std::vector<T> のイテレータは,iterator, const_iterator などとスネークケースを採用している.
     * そのため,std::vector<T>をラッパーにしたクラスもそれに準じた型名をつける.
     */
    typedef EemployeeList::iterator       iterator;    
    typedef EemployeeList::const_iterator const_iterator;


    /**
     * begin も vector に準じる
     */
    const_iterator begin() const
    {
        return employeeList_.begin();
    }


    /**
     * 追加する関数である push_back も vector に準じる
     */
    void push_back(const Eemployee &employee)
    {
        employeeList_.push_back(employee);
    }

private:
    EemployeeList_ employeeList_;
};

名前の付け方

変数やクラス関数などの名前を付けるときの共通点は以下の通りと思われる.

  • 動作やクラス,格納する情報にふさわしい名前をつける
    • これが難しいので,リーダブルコードのような本が出る
  • ハンガリアン禁止: メリットよりデメリットが多いことが知られている [C++ Code]
  • 略称禁止: 略称を使うと分からなくなる場合がある

途中でその名前を見たときコメントを見返したり機能について考えたりするような変数名ではダメということ.

スコープが小さい変数

一方でスコープが小さい変数は短くても(1文字)でもいい.[リーダブルコード]
使用する変数の有効期間が短いことをスコップが小さいといい.
宣言した直後にその変数を使用し,以降使用しない場合の変数名は1文字でもいい.

std::string s;
std::cin >> s;
std::cout << s;
// 以降,s は使わない.

名前の長さ

40文字を超える場合は適切な短い名称がないか考慮すること.逆に 40文字を超えないときは,よりストレートに他の人に伝わるか考えること(当然,短くてすぐ分かる名前が良い).この当たりはケースバイケースといえ,目安に過ぎない.

方針としては,できるだけ略称は使用せず 40文字以内にする.昨今の IDE (Visual Studio,XCode,Eclipse),高機能なエディタ(Emacs, Vim, Sublime Text)は,変数名やクラス,関数の補完機能が付いているため変数の長さはタイピング時間の妨げになりにくい.むしろ完全な名前にすることにより検索しすく,かつ,機能の目的が明確になりやすい.

省略してしても良い例は,DNS(Domain Name Server),XML(Extensible Markup Language) など一般的によく知れた名前でプロジェクト外の人でも容易に通じる名称でなければならない.DNS,XML と聞けば情報に携わるほとんどの人はDomain Name Server や Extensible Markup Language を思い出すはずである.略称を使用するにしても納得するような略称でなければならない.

名前を付けるは,非常に難しい.ネットに出回っている英和,和英,類語辞典をフルに活用すること.

禁止名称: アンダースコアの取り扱い

アンダースコアの取り扱いには十分注意すること.C++ では,以下はコンパイラなどによって予約されている.

  • グローバルスコープを持ち,_ で始まる名前
  • _ で始まり、その次が大文字の名前
  • __を含む名前

そのため,** _ 始まりはややこしいので原則禁止 ** とする.

/*
 * 予約語は,変数名でも関数でもマクロでもクラスでも全部ダメ
 */
#define Global__Value;  // NG
int __GLOBAL_VALUE;     // NG
int GLOBAL_VALUE__;     // NG
int __GLOBAL_VALUE__;   // NG
int _globalValue;       // NG
int _GlobalValue;       // NG


int main()
{
    int _localValue;     // OK
    int _local__Value;   // NG
    int _Local_Value;    // NG
}

変数

命名規則: camelCase

変数は基本的にキャメルケース.
cpp
int foo_bar; // NG.スネークケース
int fooBar; // OK.キャメルケース

bool 変数

基本的に is~has~can~, enable~ など true のときどうなっているかが明確に分かる名前にする.付けがちな flag~ は,true のときどちらかよく分からないときがある.disable~ は禁止する.

bool flagShowImage;     // NG. 画像を表示するフラグであることが分かるが,true のとき有効か無効か分かりにくい.
bool enableShowImage;   // OK. true のとき有効であることが分かる.
bool disableShowImage;  // NG. 無効が有効とややこしいため

定数

グローバル定数,名前空間内定数,静的クラス定数

特に理由が無い限り定数は,マクロではなく namespace { const ~ } を用いる.型チェックが行われるため誤った使い方をするとコンパイル時エラーが期待できる.

#define NUM_OF_EMPLOYEE   10    // NG.マクロ定数は const 変数で宣言すること!

// グローバル定数は原則禁止,必ず namespace に含めること!
namespace project_name {
const int NUMBER_OF_EMPLOYEE = 10 // OK
}

int main()
{
    int value  = NUM_OF_EMPLOYEE 1;    // おっと.NUMBER_OF_EMPLOYEE + 1 = 11 とするところが,101 になってしまう.
    int value2 = NUMBER_OF_EMPLOYEE 1;    // コンパイラエラーが発生するため実行前に分かる.
}

ローカル定数

実は,少し迷っている.

const int constFooBar;     // 変数と間違えやすいが,これかな.
const int ConstFooBar;     // const 引数と違う命名規則になるのでダメかな?
const int CONST_FOO_BAR;   // グローバルなイメージが強いので使いたくない

// 一方でこのような使い方をする
std::string addSuffix(const std::string &str, const std::string &suffix)
{
    return str + "_" + suffix;
}

関数

関数は,ある一連の手続きをまとめるときに使ったり,単純な文書でも一定の意味合いを付けるために使用する.

命名規則

原則,camelCase を用いる.例: void functionName()

引数 void のとき

原則省略する.

int function(void);    // NG
int function();        // OK. 省略しても引数がないことは十分に伝わる

クラス

クラスの分類

  • データ保持
  • 抽象型,インターフェース
  • 動作

命名規則

命名規則: PascalCase

データ保持型

データを保持することが目的(データを運んだりする)の C でいう struct の意味が強いクラスを示す.

具体的に設定をとりまとめる *Config*Setting*Option などになる.設定系の命名方針は以下の通り.

  • *Config は,システム全体の設定に関する項目やハードウェア系の設定に用いる.
  • *Setting は,ソフトウェア系の設定や *Config に至らない程度の設定に使う.
  • *Option は,設定を変更しても動作について大きな影響が出ない場合(オプション変更すると動作が停止したり結果が大きく変わるときはダメ)に使う.

データ保持型は,データを移動したりコピーしたりすることが目的であるためそれらを行う必要がある.そのため, 代入演算子,コピーコンストラクタ を正しく記述する必要がある.

struct Setting
{
    Setting() : enableShowImage()
    {
    }

    Setting(int enableShowImage

    int         enableShowImage;
    std::string showNameWindowName;
    MyClass     myClass;
};

動作

動作が入るものは,「~するもの」の意味を持たせる.("*er", "*or" など)

名称 意味合い
extractor 抽出器
detector 検出器
inspector 検査器
classifier 分類器、識別器
planner 計画器
controller 制御器
commander 指令器
messenger 送受信器
manager 管理器
coordinator 調整器

if 文

if (condition) { 
} else {
}
/*
 * 以下は禁止とする
 * 複数行の if 分は,必ず {} を付けること.
 */ 
if (condition)
    return a;
else
    return b;

1行 if

if 分の内容が簡潔であれば使用してもよい.ほとんどの場合は,関数やメソッドのエラー時に使用されることが多いと思われる.次の場合,1行 if を認める.

  • 条件文が簡潔
  • return が簡潔
  • else がない
  • 意図が明確(書式エラー,NULL など)
// 何らかの処理をして,true なら成功
bool function(const std::string &src)
{
    // src が空であれば false(失敗) を返す.
    // 条件文も意図も明確である.
    if (src.empty()) return false;
}

3項演算子

使った方が分かりやすいときは使用しても良い.強みは const, const 参照,参照が利用できること.

  1. 条件文が簡潔
  2. true/false 項が簡潔
  3. 意図が明確
13contribution

PascalCase キャメルケース 先頭大文字,それ以降の単語区切りも大文字
camelCase パスカルケース 先頭小文字,それ以降の単語区切りは大文字

説明が入れ違いになっているかと。

211contribution

typedef std::vector EemployeeList;

の部分ですが,
typedef std::vector EemployeeList_;
ではないでしょうか?