using namespace std; の危険性と注意点・代替案

using namespace std

C++では名前空間の面倒な修飾を省略するためにmain関数の外でusing namespace std;という記述をすることがあります。これによって、std::coutという記述をいつでも手軽にcoutと書けるようになり、大変便利に使えます。

using namespace std; // usingディレクティブ

int  main() { cout << "便利"; }
void test() { cout << "便利"; }

using namespace stdの利用は、サンプルプログラムで利用したり、その場限りの簡単なプログラムを書く分にはそれほど問題にはなりませんが、大規模なコードや保守期間の長いコードを書く際には出来る限り使わないようにしましょう。

目次

スポンサーリンク

using namespace std の危険性

変数名と被る

限定的ではありますが、自身が使用するの変数名とライブラリ側のシンボル名が被る場合があります。

using namespace std;

int max = max(1, 2); // エラー: Called object type 'int' is not a function or function pointer

string string;       // 問題はないが、曖昧
string.length();

string("").length(); // エラー: Type 'string' (aka 'basic_string<char, char_traits<char>, allocator<char> >') does not provide a call operator

long count = count(string.begin(), string.end(), '.'); // エラー: Called object type 'long' is not a function or function pointer
この場合、変数名stringstr等に書き直して型名と区別すると良いでしょう。これらの区別は可読性の向上に繋がるほか、ソースコード内の文字列検索時の多様性にも影響します(str 《末尾空白あり》と検索すれば、変数名のみが抽出される、等)。

近年では略称を用いない明確な命名や厳格な命名が好まれる傾向にあるため、本来であれば、countのような明確な名称をそのまま使いたい所です。そのためusing namespace stdによる変数名との名前衝突のリスクは好ましくないものと言えます。

using namespace stdの利用は、変数の命名の幅を狭めることに繋がります。

名前空間の汚染や名前衝突が発生する

using namespace stdはstd::の省略を実現するためとても便利なのですが、ただstd::stringstd::vector等のクラスも同様にstringvectorという省略された形で記述てしまう点が難点です。同一スコープ内にstringという同名クラスが別に存在していた場合、クラス名の重複が発生してしまいます(名前衝突に繋がる)。

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

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

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

またstdの場合はインクルードされるヘッダファイルが増えれば増えるほど、名前衝突のリスクが高まるという問題もあります。

ユーザ定義型との名前衝突が起こりやすい

またユーザ定義された型やシンボル名とバッティングしてしまう場合もあります。

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

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

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

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

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

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

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

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

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

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

もっとも、日頃から標準ライブラリとの整合性を意識した命名を心がけることが第一です。

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

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

namespace STD {
   template<class T> void fn(T&& v) {}
}

void fn(const std::string& v) {}

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

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

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 の代替案

個別に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("a");
   }
   cout << string("a"); /// エラー:Use of undeclared identifier 'cout', 'string'
}

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

int main() {
   cout << string("a"); // エラー
   using namespace std;
   cout << string("a"); // エラーにならない
}

名前空間内で利用する

独自の名前空間で利用した場合、空間内で定義されたの全ての関数/クラスでライブラリの利用が可能になります。

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

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

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

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

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

namespace lib { using namespace std; }
using namespace lib;

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

無名名前空間では危険

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

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

広告