usingディレクティブ|using namespace std; の危険性と注意点・代替案

using namespace std

C++では名前空間の面倒な修飾を省略するためにmain関数の外でusing namespace std;という記述をすることがあります。これによって、std::coutという記述をcoutというstd::を省略した形で簡潔に記述できるようになります。

#include <iostream>
using namespace std; // usingディレクティブ

int main() { cout << string("便利") << endl; }

# usingディレクティブ、とりわけusing namespace stdの利用は、サンプルプログラムでの活用や、その場限りの簡単なプログラムを書く分にはそれほど問題にはなりませんが、大規模なコードや継続的な保守や拡張を前提としたコードを書く際には、面倒な問題や複雑な挙動に直面する危険性があるため、できる限り使わないようにすることをオススメします。

usingディレクティブの多用は、名前空間の汚染や機能名の競合が発生する問題、標準型とユーザ定義型の区別が曖昧になる問題など、様々な問題を引き起こすことにも繋がるため、仮に利用する場合には注意して慎重に扱う必要があります。問題が起こらないことが明確なケースでのみ利用するのがよいでしょう。

目次

スポンサーリンク

usingディレクティブ

このusing namespace 名前空間名;による記述はusingディレクティブ(using directive)と呼ばれます。特定のスコープ内でusingディレクティブが記述されると、以降のスコープ内では名前検索の範囲が指定された名前空間内にまで拡張されるという特徴があります。

C++標準ライブラリの名前空間名であるstdがusingディレクティブに指定された場合、同空間内のメンバであるstd::coutオブジェクトやstd::stringクラス、std::max関数等が、std::による名前修飾無しに利用できるようになります。

#include <iostream>  // std::cout
#include <algorithm> // std::max
#include <string>    // std::string

int main() {
  using namespace std;
  cout << string("abc", 0, max(1, 2));
  
  // 以下と同等の記述
  std::cout << std::string("abc", 0, std::max(1, 2));
}

ちなみに、usingディレクティブは名前空間内や関数スコープ、その他のスコープ内でも利用できますが、クラススコープでは利用できないという特徴があります。

usingディレクティブは基本的に、ヘッダーファイル内やグローバルスコープ内、とりわけヘッダファイル内では利用しないようにし、仮にファイルスコープ内や個別の名前空間内で扱う場合には、名前衝突等の問題が起こらないことが明確な場合にのみ慎重に扱うことをオススメします。

このusingディレクティブの利用によって生じる様々問題については、次項以降でusing namespace stdの記述を例に解説していますので参考にしてください。

using namespace std の危険性

変数名と被る

std::を省略した記述を前提にコード書いてしまうと、ライブラリ側のシンボル名と自身が宣言した変数名が被ってしまう恐れがあります。

using namespace std;

// エラー: Called object type 'int' is not a function or function pointer
int max = max(1, 2);
// エラー: Called object type 'long' is not a function or function pointer
long count = count(begin(string), end(string), '-');
// エラー: Variable 'begin' declared with 'auto' type cannot appear in its own initializer
auto begin = begin(string);

近年では略称を用いない命名が好まれる傾向にあるため、本来であれば、count変数やbegin変数のような明確な名称をそのまま使いたい所です。そのためusingディレクティブの利用によって生じる変数名と機能名の名前衝突のリスクは好ましくないものと言えます。usingディレクティブの乱用は、変数名の命名の幅を狭めることにも繋がります。

ちなみに変数宣言時の型名と変数名については名前衝突が発生しません。ただしコンストラクタ呼び出しは行えなくなります。

string string;       // 問題はないが、曖昧
string.length();
// エラー: Type 'string' (aka 'basic_string<char, char_traits<char>, allocator<char> >') does not provide a call operator
string("").length(); // クラスのコンストラクタ呼び出しにはならない
この場合、変数名stringstr等に書き直して型名と区別すると良いでしょう。これらの区別は可読性の向上に繋がるほか、ソースコード内の文字列検索時の多様性にも影響します(str 《末尾空白あり》で文字列検索すれば、変数名のみが抽出される)。

ユーザ定義型との名前衝突が起こる

ユーザ定義された型名やシンボル名がstd側のメンバ名と重複してしまう場合もあります。

using namespace std;
struct string {}; /* std::stringと同名クラスを独自定義 */
string str; // エラー: Reference to 'string' is ambiguous

このような問題に対処するためには、シンボル名の変更(例: stringmy_string ─ クラスプレフィックスの指定)や命名規則の見直し(例: stringString ─ ユーザ定義型はキャメルケースで統一する等)が必要となります。

またはusing namespace std;の利用自体を取りやめるという決断を迫られる場合もあります。その場合、既存のコードを大幅に修正する必要が生まれます(既存のstringstd::stringに書き直したりする。または必要なもののみを個別にusing宣言するようにする)

ちなみにstdの場合はインクルードされるヘッダファイルが増えれば増えるほど、名前空間の汚染が拡大し、名前衝突のリスクもより高まりやすくなるという問題があります。加えて、今後標準ライブラリに新しい関数やクラスが追加された場合のリスクにも注意しなければならなくなります。

ユーザ定義型との区別が曖昧になる

自身が定義した同名型との区別が曖昧になるというややこしい問題もあります。

namespace nm {
   struct string {};
   using namespace std;
   string str; // std::stringではない(ややこしい)
}

この曖昧さは、勘違いやミスの原因にもなりやすく危険です。

また標準ライブラリ側とユーザ定義側のどちらの機能が使われているのかの区別が付きにくくなるという問題もあります。これによってコード解読時やコードレビュー時に余計な精査コストを生んでしまう恐れがあります。

// 自作の`move`関数を呼んでいるのか
// または`std::move`が呼ばれているのか
// どちらなのか区別が付かない
f(move(obj));

このようなケースでは、きちんと名前空間を明示してstd::moveと書くようにすると良いです。std::が明示されている完全修飾名の方が標準ライブラリ側の関数で、明示されていないほうが自作関数であるという区別が可能になります。

多重定義された関数が呼ばれなくなる場合がある

オーバーロード解決のケースによっては、読み手の意図に反する挙動を取ることがあります。これは思わぬ勘違いやバグにつながる危険性があるため注意が必要です。

namespace STD {
   template<class T> void f(T&& v) {
      puts("STD::f(T&&)");
   }
}

void f(const std::string& v) {
   puts("f(const std::string&)");
}

int main() {
   const std::string a = "";
   /* */ std::string b = "";
   
   f(a);             // f(const std::string&)
   f(b);             // f(const std::string&)
   f(std::string()); // f(const std::string&)
   
   using namespace STD;
   
   f(a);             // f(const std::string&)
   f(b);             // STD::f(T&&)
   f(std::string()); // STD::f(T&&)
}

以上のことから、グローバル空間や特定の名前空間内におけるusingディレクティブの利用/採用は慎重に行う必要があります。小規模な開発シーンではそれほど問題にはならないですが、大規模なプロジェクトや複数のライブラリを併用する場面では可能な限り利用しないようにすることをオススメします。

using namespace std 利用時の注意点

using namespace stdを用いる場合は先程の「# 多重定義された関数が呼ばれなくなる場合がある# ユーザ定義型との区別が曖昧になる」の問題や、以下の特性を理解した上で使うようにしましょう。

スコープ別の挙動に注意する

名前空間内での利用と、グローバルスコープ/ファイルスコープでの利用で若干挙動の変化が発生するため注意してください。名前空間内でユーザ定義を行った場合には、グローバルスコープ時/ファイルスコープ時のようなエラーは発生しませんが、名前解決の方法が変わります。コードをリファクタリングしたり、移植したりする際にはこれらの違いや特性に十分注意して慎重に行う必要があります。

グローバルスコープ時/ファイルスコープ時

using namespace std;

// ユーザ定義型のクラスと関数
struct string {};
template<class T> T move(T v) { return v; }

int main() {
   string t{};   // きちんとエラーになる(Reference to 'string' is ambiguous)
   move(9);      // きちんとエラーになる(Call to 'move' is ambiguous)
   std::move(9); // std::move を明示的に呼び出せば問題ない
   ::move(9);    // ユーザ定義の関数が呼ばれる
   ::string s{}; // ユーザ定義のクラスが使われる
}

名前空間内

using namespace std;

namespace nm {
   // ユーザ定義型のクラスと関数
   struct string {};
   template<class T> T move(T v) { return v; }
   
   void main() {
      move(9);      // ユーザ定義の関数が呼ばれる
      string t{};   // ユーザ定義のクラスが使われる
      ::move(9);    // std::move が呼ばれる
      ::string u{}; // std::string が使われる
   }
}

複数の名前空間を取り込む際のリスク

異なる名前空間同士に存在する同名のメンバが同一スコープ内に展開された際に、クラス名の名前衝突が発生してしまう問題もあります。

using namespace std; /* I have a std::string */
using namespace xml; /* I have a xml::string */

string str = "ugh!"; // エラー:Reference to 'string' is ambiguous

この名前衝突の問題については、最初の内は問題になっていなくても、いずれ双方のライブラリ側で新しい機能名が追加されて、名前衝突に繋がてしまうケースが想定されます。その場合、既存のソースコード内の重複した機能名に対して名前空間の明示が必要となります(例: stringstd::string)。場合によっては「std以外の空間についてはusingディレクティブ用いないようにする」等の取り決めが必要になります。

using namespace std の代替案

個別にusingする

以下のように必要な物のみを宣言すれば、名前空間を無駄に多く汚さずに済みます。

using std::cout;
using std::string;
using strings = std::vector<std::string>;
template<typename T> using vector = std::vector<T>;

使い方やより詳しい説明は以下のページで解説していますので参考にしてください。

関数やクラスを個別にusingする方法

局所的にusing namespace stdする

どうしてもstd内のメンバを一括で利用できるようにしたいような場合は、関数内や名前空間内などの個別のローカル空間で利用すると比較的安全です。

関数内で利用する

関数内のローカルスコープで利用すれば、関数内でのみ全てのライブラリ関数やクラスを活用することが可能になります。その他の関数やスコープに名前が汚染することもありません。

// using namespace std; // 危険(ファイルスコープに侵食する)
int main() {
   using namespace std; // この程度の処理なら比較的安全
   cout << 99 << endl;
}

ブロックスコープで利用する

if文やfor文、ブロック文等のブロックスコープ内で利用することも可能です。

int main() {
   if (true) {
      using namespace std;
      cout << string("a");
   }
   {
      using namespace std;
      cout << string("b");
   }
   // エラー:Use of undeclared identifier 'cout', 'string'
   cout << string("c");
}

特定のタイミングで利用する

usingディレクティブは任意の行に記述することができます。usingディレクティブの効果はusingディレクティブが記述された位置以降のスコープでのみ有効となります。

int main() {
   cout << string("a"); // エラー: この行ではまだ有効ではない
   using namespace std;
   cout << string("a"); // OK
}

名前空間内で利用する

usingディレクティブを独自の名前空間内で利用した場合、取り込まれたメンバは自身の空間内で定義されたの全ての関数/クラス内で利用可能となります。

namespace lib {
   using namespace std; // それなりに安全
   void print(string s) { cout << s << endl; }
   class T { void f() { cout << 9; } };
}

ただし空間内のコードが複雑で膨大になればなるほど、先程挙げた名前衝突やあまいさのリスクが高まります。またリファクタリング時には先程の「# 多重定義された関数が呼ばれなくなる場合がある」で紹介したリスクにも注意する必要があります。

無名名前空間での利用に注意

無名名前空間/匿名名前空間の場合はファイルスコープへの名前汚染を引き起こしてしまうため注意してください。

namespace {
   using namespace std; // 危険
}
string s; // 使用できてしまう

独自定義の名前空間でも注意する

using namespace std を活用した独自定義の名前空間に対して、usingディレクティブを用いる際にも注意が必要です。

以下のケースでは名前空間libのメンバだけでなく、連鎖的にstdのメンバもファイルスコープやグローバルスコープへ汚染します。

namespace lib { using namespace std; }

using namespace lib;

int main() {
   cout << string("stdのメンバも汚染してる");
}

広告

広告