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 namespace記法|using宣言との違い】

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(); // クラスのコンストラクタ呼び出しにはならない
この場合、変数名stringstrtext等に書き直して型名と区別すると良いでしょう。これらの区別は可読性の向上に繋がるほか、ソースコード内の文字列検索時の多様性にも影響します(str 《末尾空白あり》で文字列検索すれば、変数名のみが抽出される)。もっとも、usingディレクティブを用いなければこのような区別は不要となります。

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

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

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

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

暫定的な処置としては、スコープ解決演算子(::)による名前空間の明示や、using宣言を行うことなどが考えられます(# 名前競合への対処)。

using namespace std;
struct string {};
::string str;    // ファイルスコープ側の`string`型
std::string str; // 名前空間`std`側の`string`クラス

別の対策としては、シンボル名の変更(例: stringmy_string ─ クラスプレフィックスの指定)や命名規則の見直し(例: stringString ─ ユーザ定義型はキャメルケースで統一する等)などが考えられます。

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

根本的な対策は、using namespace 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;
struct string {};
::string s;    // ファイルスコープ側の`string`型
std::string t; // 名前空間`std`側の`string`クラス

他にも、using宣言を行う方法があります。using宣言による再宣言により名前探索時の優先順位が明確化するため、名前競合を回避することに繋がります。

using namespace std;
struct string {};

void f() {
   using std::string;
   string s; // `s`は`std::string`型
}
void g() {
   using ::string;
   string s; // `s`は`struct string`型
}

特定の名前空間内でusing宣言を行えば、一括で名前解決を行うことができます。クラススコープでusing宣言が行えない問題の対応策としても有効です。

using namespace std;
struct string {};

namespace ns {
   using std::string;
   struct T {
      void g() { string s; } // `s`は`std::string`型
      void f() { string s; } // `s`は`std::string`型
      void h() {
         using ::string;
         string s; // `s`は`struct string`型
      }
   };
}

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のメンバも汚染してる");
}
広告