474
403

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

namespaceの賢い使い方

Last updated at Posted at 2015-12-23

#namespaceの賢い使い方

みんな大好き(?)、いなむ先生です。
本記事は初心者 C++er Advent Calendar 2015 6日目の穴埋め記事です。

注意書き
本記事は一般的なコーディングを賢く行うためのTips的記事です。

名前空間

名前空間は名前の衝突を防ぐために必要なものです。
namespaceキーワードにつづいてお好きな名前を書き続いて{}で囲みます。
この中で書かれた名前は、外から呼ぶときには先頭に名前空間の修飾をつけなければなりません。
これにはoperator::(スコープ解決演算子と呼ばれる)を用います。

以下のコードを見てください:

namespace A
{
    void f(){std::cout << "A" << std::endl;};
    void hoge(){
        f(); // 名前空間の中では修飾しなくても呼び出せる
    }
}

namespace B
{
    void f(){std::cout << "B" << std::endl;};
    void hoge(){
        A::f(); // 別の名前空間から呼びたいなら修飾する
    }
}

int main()
{
    A::f(); // A
    B::f(); // B
    A::hoge(); // A
    B::hoge(); // A
}

汎用性の高い名前を用いても名前を区別することができます。
(check, get, etc...)

標準ライブラリはstd名前空間に定義されいます。
呼び出す場合は適切なヘッダをインクルードし、
std::で修飾して上げる必要があります。

いちいち名前空間で修飾するのは面倒ですので

using namespace std ;

と書いてしまったコードもよく見かけます。

しかし、せっかく名前空間に宣言してあるコードを自分の名前空間やグローバルにすべて持ち込んでしまうのはあまりにお粗末です。

##名前の衝突

その理由の一つは、標準の関数と同じ名前(厳密にはシグネチャのこと)の関数を書いてしまった場合に起こります。

例:max
せっかくstd::maxという名前だったのにusing namespace std;のせいでstd::maxが単なるmaxとおなじになり、自分の書いたmaxと区別がつかなくなる、ということが起こりえます。

#include <iostream>

using namespace std;

template < typename T >
T max(T const& a, T const& b)
{
    return a < b ? b : a ;
}

int main(){
    cout << max(1,2) << endl; // エラーだよ
    return 0;
}

この例では、std::cout, std::endl;cout, endlと書きたいだけでmaxのことは考えていませんがstd名前空間にはmaxが存在します。

標準にある関数名なんか書かないよと思った人いるかもしれません。
標準ライブラリはとてもとても多くの名前がありますので把握は不可能ですし、C++のバージョンを上げることも考えると名前が増えます。
例えばC++14まではstd::gcdはありませんでしたからusing namespace std;した上でgcdを書いていても大丈夫でしたが、C++17を使うとアウトになります。

##namespaceの短縮(エイリアス)

名前空間に別名をつけることができます。

namespace ns = very_very_long_namespace;

のようにすれば、長い名前空間を短くできます。

namespace hoge = a::b::c::hoge;

のように深い名前空間をユーザーが別名をつけて使うのが一般的です。

###Using 宣言

using namespaceしてしまうと、名前空間にある名前が全てスコープ内に持ち込まれます。
上記の名前の衝突などが起こりえます。

そこで、次のように使う名前だけをusing 宣言します。

using std::cout ; // std::cout を cout と書ける
using std::endl ; // std::endl を endl と書ける

これで影響を最小限にすることができます。

例えば、ある関数内でstd::begin, std::endを使う場合:

template < class Range >
void f(const Range & range){
  using std::begin; // for ADL
  using std::end; // for ADL

  auto first = begin(range);
  auto last  = end(range);
  
  // ...
}

のようにすることでusingによる名前の持ち込みを関数のスコープ内に留めることができます。

またこのusingの使い方はオーバーロードの可視化によってADLのcandidate functionになるという利点があります。

実はusing std::beginしなくても

std::vector<int> v;
auto first = begin(v);

のようにstd::beginとしなくてもvectorにbeginを適用することができます。
これはおなじstd名前空間にvectorとbeginが存在するからです。

関数(引数)

のように呼び出した場合。
引数の型が宣言されている名前空間から関数を検索するのです(雑
これを ADL(Argument Dependent Lookup) といいます。

ところで
begin()は int[10] のような配列にも使えますが int[10] は組み込み型なので名前空間に宣言されているわけではありません。

よって以下はエラーです:

int arr[10];
begin(arr); // error

しかしusing std::begin;しておけばbegin(arr)としても問題ありません、鉄壁です。

これだけだとstd::begin使っておけばいいと思うかもしれません。

しかし次の場合を考えてください:


namespace my_space {
  // 独自配列
  struct MyArray{ /* ... */};
  // MyArray専用begin
  decltype(auto) begin(const MyArray& a) { return /* ... */; }
}

template < class Range >
void f(Range&& range){
  using std::begin;
  auto first = begin(range);
}


fの引数はテンプレートなのでvectorかもしれませんし組み込みの配列かもしれませんし、ユーザー定義型の配列 かもしれません!
もしMyArrayが引数ならstd::beginではなくmy_space::beginを呼ばなければなりません。

using std::beginした上でbegin(range)を書けば、MyArrayの場合ADLでmy_space::beginが、vectorの場合も同様にstd::beginが配列の場合でもstd::beginはusingされているのでちゃんと呼ばれます。
という理由でこれが鉄壁の方法です。

基本的にヘッダに書くコード(ライブラリ)は名前空間の中に記述することが必要でしょう。

その理由の一つはこのグローバルに関数があると意図しないオーバーロード解決の対象になってバグの原因なるからです。

namespaceを賢く使いましょう。

inline namespace

inline指定された名前空間は名前空間の修飾を省略できます。
もちろん修飾つきで呼ぶこともできます。

inline namespaceは上級者向けの機能です。
使いみちは主に以下の2つです:

  • ライブラリ等でのAPIのバージョン管理

  • using namespaceによる名前空間省略の階層の段階的管理

ライブラリ等でのAPIのバージョン管理

inline namespace を使うことによってデフォルトの関数を切り替えるという使い方です。

#include <iostream>

namespace my_lib {
  namespace v1 {
    void f()
    {
      std::cout << "v1" << std::endl;
    }
  }

  inline namespace v2 {
    void f()
    {
      std::cout << "v2" << std::endl;
    }
  }
}

int main()
{
  my_lib::v1::f(); // 古いバージョンのAPIを呼び出す
  my_lib::v2::f(); // バージョンを明示的に指定してAPIを呼び出す
  my_lib::f();     // ライブラリデフォルトのAPIを呼び出す
}

using namespaceによる名前空間省略の階層の段階的管理

おそらくUDLs(User Defined Literals)を書くときに一番使うのです。
なぜなら、UDLsは名前空間のusing namespaceを前提としているからなのです。

namespace my_literals {
inline namespace integer_literals {
inline auto operator"" _kilo(unsigned long long x)
  { return int(x) * 1'000; }
}
inline namespace floating_point_literals {
inline auto operator"" _kilo(long double x)
  { return double(x) * 1'000; }
}
}

int main() {
    {
        using namespace my_literals::integer_literals; // int用だけ可視化される
        1_kilo; // 1000
    }

    {
        using namespace my_literals::floating_point_literals; // double用だけ可視化される
        1_kilo; // 1000.0
    }

    {
        using namespace my_literals; // 全部可視化される
        1_kilo; // 1000
        1.0_kilo; // 1000.0
    }
}

UDLsにしても普通の関数のAPI群にしてもinline namespaceに格納しておけば可視化の範囲をユーザーが選択できるというメリットがあります。

##跋文

ミス、間違いがありましたらいなむ先生までツイートしていただくかかコメントをいただけると嬉しいです。
できるだけ迅速に修正致します

474
403
1

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
474
403

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?