LoginSignup
148
108

More than 5 years have passed since last update.

C++におけるおすすめ命名規則

Posted at

はじめに

命名規則には宗教上の問題が絡みます。この記事はどの命名規則がより優れているというものではなく、あくまでも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++の命名規則について宗教的な部分には触れないようにしてきたつもりでしたが、いかがだったでしょうか。誤りなど見つけたらぜひコメントを頂けると助かります。
命名規則についての論争を目的とした記事ではないのでそちらについてのコメントはお控えください。

最後まで読んでいただきありがとうございました。

148
108
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
148
108