🌑

「using namespace std」に潜む闇

に公開1

最近見ているコードでusing namespace std;をたくさん見かけます。C++ 標準ライブラリのクラスや関数を毎回std::を書かずに使えるというメリットがあるため、使いたくなるのは分かりますが、以下のようなデメリットもあります。

  1. コードが読みづらくなります。名前空間はクラスや関数の区別には大事な情報です。
    • vectorstringはみんなもう見慣れているからすぐ分かると思いますが、minmaxとかis_sortedなどを見て一瞬で C++ 標準ライブラリの関数だと分かる人は少ないと思います。名前空間を省略してしまうと、そういうありきたりな名前はどんどん被る確率が上がっていきます。クラスや変数だったら、定義できなくなったり、関数の場合は、どちらの関数をコールしているのかがコード上だけだと分かりづらくなったりします。でも殆どの場合は、コンパイラが区別がしてくれるので、コードが読みづらくなるだけで、動作的には問題ないはずです。コンパイラも区別できない場合は、ちゃんとコンパイルエラーになります。
  2. 本当にまれに、名前空間のクラスや関数の定義が微妙に似ている場合があります。
    • 完全一致だったらコンパイラもどちらを呼べばいいのか分からなくなりコンパイルエラーになるのですが、微妙に違う場合は、コンパイラの内部の優先度によって最適な方が呼ばれてしまいます。

2のケースを実際に再現してみましょう。

foo.h
namespace foo {
    void f(float) { std::cout << "called foo::f" << std::endl; }
}

まずはこのfoo::fという関数が以下のように使われているとしましょう。

main.cpp
using namespace foo;
...
int main()
{
   int number;
   f(number); // foo::f(float) がコールされる
}

その後に以下のbar::fという関数が定義されているヘッダをインクルードします。

bar.h
namespace bar {
    void f(int) { std::cout << "called bar::f" << std::endl; }
}

barusing namespaceで書かなくてもいいようにします。

main.cpp
using namespace foo;
using namespace bar;
...
void do_something(int number)
{
   f(number); // foo::f(float) , bar::f(int) どっち??
}

foo::fbar::fの定義は違うため、コンパイルエラーにはならないです。

コンパイラ的にbar::fの方がint型の引数があるため最適だと判断され、今までの動作が静かに変わってしまいます。

「いや、そんな偶然あるか?わざわざ両方をインクルードしないといけないでしょ?その時に流石に気づくでしょ?」

main.cppからインクルードする場合はそうかもしれませんが、bar::fが定義されているヘッダを別のクラスや関数目当てでインクルードしているかもしれないです。
しかも、以下のように偶然bar.hをインクルードしているヘッダをインクルードすることもあり得るので、かなり厄介です。

「でも同じ名前で微妙に似ている定義ってそんなにあるか?」

Boostのようなライブラリを使ったことがある人はご存じだと思いますが、そのような C++ 標準ライブラリを拡張しているライブラリは、名前が被っているクラスや関数が山ほどあります。しかも、拡張しているだけあって、定義もわざと似せている定義が多いです。
例えば、threadoptionalvariantstdにもboostにも存在します。

https://cpprefjp.github.io/reference/thread/thread.html
https://boostjp.github.io/tips/thread.html

これはstdだけではなく、どの名前空間にも言えることで、やはり名前空間という大事な情報をusing namespaceでできるだけ省略しない方がいいと思います。

名前空間が多すぎて全部書くと長すぎる場合は、以下のように名前空間エイリアスを使いましょう。

namespace bf = boost::filesystem;
bf::path p;

それか、usingを使って、実際に使いたい名前だけ名前空間なしで使えるようにするのもいいですね。

using boost::filesystem::path;
path p;

でもこの場合は、実際に使いたいところだけに限定して、できるだけ小さいスコープにしましょう。


|cpp記事一覧へのリンク|

Discussion

dameyodamedamedameyodamedame

別に闇でもなく、C++を作った本人監修のガイドラインにも以下のようにあります。

中を引用すると、

Reason

using namespace can lead to name clashes, so it should be used sparingly. However, it is not always possible to qualify every name from a namespace in user code (e.g., during transition) and sometimes a namespace is so fundamental and prevalent in a code base, that consistent qualification would be verbose and distracting.

とあり、翻訳した内容は以下の通りです。

理由

using namespace名前の衝突につながる可能性があるので、控えめに使用する必要があります。 ただし、ユーザーコードの名前空間からすべての名前を修飾するとは限りません(たとえば、移行中)。 そして時々、名前空間はコードベースで非常に基本的で普及しているので、一貫した資格は冗長で気を散らすでしょう。

冗長で気を散らすので、実装コード上で衝突しないのであれば、使っていいと読めます。ただしヘッダオンリーライブラリなどでグローバルスコープに書くとかは弊害が大きいのでやめましょう。