#はじめに
命名規則には宗教上の問題が絡みます。この記事はどの命名規則がより優れているというものではなく、あくまでもC++における予約された名前を確認して未定義動作を避け、かつC++標準ライブラリに合わせた命名規則をC++におけるおすすめの命名規則として紹介するというものです。
最終的には自らの信仰の自由に従いましょう。
なお、この記事ではそれぞれの記法を以下のように呼ぶこととします。
呼び方 | 例 |
---|---|
スネークケース | snake_case |
キャメルケース | camelCase |
パスカルケース | PascalCase |
断りとして、C++では予約されていて使うことのできない名前を以下に示しておきます。どの命名規則を使うにしろ、C++では以下の名前は使ってはいけません。
予約済みの名前 | ダメな例 |
---|---|
アンダースコア+大文字で始まる名前 | _INCLUDE_GUARD_ |
連続する2つのアンダースコアを含む名前 | my__library |
大域空間においてアンダースコアで始まる名前 | ::_my_func |
std名前空間の中に置かれる名前 | ::std::my_func |
例外的に、std名前空間に中にユーザー定義型に対する標準ライブラリの型の完全/部分を問わない特殊化を記述することが許されています。
namespace std
{
template<>
hash< my_class >
{
// 省略...
};
}
また、ユーザー定義リテラルでは上に加えて_
から始まらない識別子が全て予約されています。言い換えれば、ユーザー定義リテラルは必ずアンダースコアから始めなければなりません。
int operator "" my_literal(unsigned long long n); // だめ
なお、ユーザー定義リテラルの名前はoperator "" xxxx
ですので、上の予約済みの名前の制限を一部受けません。
// グローバルスコープ
int operator "" _my_literal(unsigned long long); // ok; 名前は 'operator "" _my_literal' となる
int operator "" _My_literal(unsigned long long); // ok; 名前は 'operator "" _My_literal' となる
int operator "" _my__literal(unsigned long long); // だめ。連続する2つのアンダースコアを含む名前は予約されている。
#C++では基本的にスネークケースを使おう
C++標準ライブラリは名前空間、型名、変数名、関数名の全てにスネークケースを使って命名しています。よってスネークケースを用いると全体の統一感がでて目に優しいコードを書くことができます。
この考え方は他の言語についても言えて、JavaならJavaの標準のライブラリの命名規則を、C#ならC#の標準のライブラリの命名規則を用いてコードを書くことでまず間違いないコードを書くことができます。
ただし、プロジェクトによって命名規則が定められている場合にはこの限りではなく、それに従わなければならないでしょう。
##たとえば
// 名前空間
namespace numeric
{
// typedef
using radian = double;
// クラス
class fraction;
// 定数
constexpr double pi = 3.14;
// 関数
constexpr double sin(radian);
}
##クラスのメンバの命名
クラスのメンバ、特にプライベートメンバに対して接頭辞や接頭辞をつけるという主義があります。それぞれのケースを比較してみましょう。どれも一長一短ですが、裏にある思想が見え隠れする部分でもありますので、自分の好みが強く発揮される部分でもあると思います。
何もつけないよ派
シンプルに何もつけないよ派です。逆にゲッターやセッターにはget_
やset_
などの接頭辞をつけなくてはなりません。しかしそれが意図を明確にしているともいえます。
この派閥はオブジェクトの性質を問い合わせるメンバにはis_
を付けるなど、オブジェクトが主語でメンバ関数が動詞という意識がはっきりしている人が多い印象があります。
class vec2d
{
double x, y;
public:
vec2d() = default;
vec2d(double x, double y) : x(x), y(y) {}
double get_x() const { return x; }
double get_y() const { return y; }
vec2d& set_x(double x) { this->x = x; return (*this); }
vec2d& set_y(double y) { this->y = y; return (*this); }
bool is_empty() const { return !x && !y; }
プライベートメンバ変数の頭にm_を付けるよ派
プライベートメンバにm_
の接頭辞をつけ、ゲッターやセッターがより簡易な名前を得るようにしている派閥です。mはmemberの略ですが、メンバのうちプライベートのものにのみmemberの接頭辞がつくという統一感のなさを嫌って_
という接頭辞を付ける人もいます。
class vec2d
{
double m_x, m_y;
// double _x, _y;
public:
vec2d() = default;
vec2d(double x, double y) : m_x(x), m_y(y) {}
double x() const { return m_x; }
double y() const { return m_y; }
vec2d& x(double x) { m_x = x; return (*this); }
vec2d& y(double y) { m_y = y; return (*this); }
bool empty() const { return !m_x && !m_y; }
};
###プライベートメンバのお尻に_をつけるよ派
上と比較して、アンダースコアが接尾辞になっている派閥です。基本的な思想は_
の接頭辞を付ける人と同じだと思われますが、パスカルケースを使いたい場合_
の接頭辞では予約された名前を使うことになってしまうのに対して接尾辞であれば問題がないという違いがあります。ただし、アンダースコアは元々単語と単語の区切りに使われるものなので、接尾辞に使った時の尻切れ感を嫌う人もいます。
class vec2d
{
double x_, y_;
// 事情によりパスカルケースを使いたい場合
// double m_X, m_Y; // ok
// double _X, _Y; // oops, _X is one of the reserved names.
// double X_, Y_; // ok
public:
vec2d() = default;
vec2d(double x, double y) : x_(x), y_(y) {}
double x() const { return x_; }
double y() const { return y_; }
void x(double x) { x_ = x; }
void y(double y) { y_ = y; }
bool empty() const { return !x_ && !y_; }
接頭辞や接尾辞を付ける派閥はその目的上名詞や形容詞のメンバ関数を容認します。この点で言えば、標準ライブラリはこの派閥に分類できると思います。
どれも一長一短なのでプロジェクトで定められていなければどれを用いてもいいと思います。
例外1: テンプレート引数
テンプレート引数は例外的にパスカルケースが用いられることが多いです。テンプレートクラスではテンプレート引数を外部から直接参照することはできないのでテンプレート引数にはパスカルケースを用いておき、それをtypedefでスネークケースにして外部に公開する、というような具合ですね。
// どの型でも構わない場合は T が慣習的に使われることが多いです。
template< class T >
class pointer_wrapper
{
public:
using value_type = T;
// 省略...
};
// 明示した方がいい場合はちゃんと書いた方がいいですね。
template< class NumeratorType, class DenominatorType >
class fraction
{
public:
using numerator_type = NumeratorType;
using denominator_type = DenominatorType;
// 省略...
};
// 型に限らずテンプレート引数はパスカルケースが多いです。
template< class T, std::size_t N >
class array
{
public:
using value_type = T;
using size_type = std::size_t;
constexpr size_type size() const noexcept { return N; }
};
// 関数テンプレートでもテンプレート引数はパスカルケース。
template< class T, std::size_t N >
constexpr std::size_t size(T(&array)[N]) noexcept
{
return N;
}
// 可変長引数の名前はArgが多いと思います。
template< class Func, class... Args >
constexpr auto apply(Func f, Args&&... args)
{
return f(std::forward< Args >(args)...);
}
// メタプログラミングでは珍しい名前が見られることも
template< class Head, class... Body >
struct first_type
{
using type = Head;
};
// VisualStudioのヘッダを覗いて真似して以下のように書くのはダメ。絶対。
// あれは処理系なので予約された名前を使えるのだ。
template< class _Ty >
struct is_void
{
static constexpr bool value = false;
};
template <>
struct is_void< void >
{
static constexpr bool value = true;
};
こうしてみると、テンプレート引数にパスカルケースを用いるのはただの慣習であり、外部から直接参照できないからtypedefと衝突しないように~とかは後付けの理屈のように見えますね。しかしテンプレート引数と他を区別するのはそれでもやはり有用だと私は思います。
#例外2: マクロ
マクロは例外で、全て大文字のスネークケースを用います。例えばインクルードガードや、条件コンパイルに使うフラグなどです。大文字のスネークケースである識別子は全てマクロであるという状態を作っておけば、マクロによる意図しない置き換えを防ぐことができます。
#ifndef DIRECTORY_MY_LIBRARY_HPP
#define DIRECTORY_MY_LIBRARY_HPP
#if defined(DEBUG)
// デバッグ用のコード
#else
// リリース用のコード
#endif
#endif // !DIRECTORY_MY_LIBRARY_HPP
ここを徹底しなければ、<windows.h>
のmin() max()
マクロのような間違いが起こります。これのせいで未だにポータビリティを大切にする場合(std::numeric_limits< int >::max)()
のようなコードを書いています。
#おわりに
C++の命名規則について宗教的な部分には触れないようにしてきたつもりでしたが、いかがだったでしょうか。誤りなど見つけたらぜひコメントを頂けると助かります。
命名規則についての論争を目的とした記事ではないのでそちらについてのコメントはお控えください。
最後まで読んでいただきありがとうございました。