「using namespace std」に潜む闇
最近見ているコードでusing namespace std;
をたくさん見かけます。C++ 標準ライブラリのクラスや関数を毎回std::
を書かずに使えるというメリットがあるため、使いたくなるのは分かりますが、以下のようなデメリットもあります。
- コードが読みづらくなります。名前空間はクラスや関数の区別には大事な情報です。
-
vector
やstring
はみんなもう見慣れているからすぐ分かると思いますが、minmax
とかis_sorted
などを見て一瞬で C++ 標準ライブラリの関数だと分かる人は少ないと思います。名前空間を省略してしまうと、そういうありきたりな名前はどんどん被る確率が上がっていきます。クラスや変数だったら、定義できなくなったり、関数の場合は、どちらの関数をコールしているのかがコード上だけだと分かりづらくなったりします。でも殆どの場合は、コンパイラが区別がしてくれるので、コードが読みづらくなるだけで、動作的には問題ないはずです。コンパイラも区別できない場合は、ちゃんとコンパイルエラーになります。
-
- 本当にまれに、名前空間のクラスや関数の定義が微妙に似ている場合があります。
- 完全一致だったらコンパイラもどちらを呼べばいいのか分からなくなりコンパイルエラーになるのですが、微妙に違う場合は、コンパイラの内部の優先度によって最適な方が呼ばれてしまいます。
2のケースを実際に再現してみましょう。
namespace foo {
void f(float) { std::cout << "called foo::f" << std::endl; }
}
まずはこのfoo::f
という関数が以下のように使われているとしましょう。
using namespace foo;
...
int main()
{
int number;
f(number); // foo::f(float) がコールされる
}
その後に以下のbar::f
という関数が定義されているヘッダをインクルードします。
namespace bar {
void f(int) { std::cout << "called bar::f" << std::endl; }
}
bar
もusing namespace
で書かなくてもいいようにします。
using namespace foo;
using namespace bar;
...
void do_something(int number)
{
f(number); // foo::f(float) , bar::f(int) どっち??
}
foo::f
とbar::f
の定義は違うため、コンパイルエラーにはならないです。
コンパイラ的にbar::f
の方がint
型の引数があるため最適だと判断され、今までの動作が静かに変わってしまいます。
「いや、そんな偶然あるか?わざわざ両方をインクルードしないといけないでしょ?その時に流石に気づくでしょ?」
main.cpp
からインクルードする場合はそうかもしれませんが、bar::f
が定義されているヘッダを別のクラスや関数目当てでインクルードしているかもしれないです。
しかも、以下のように偶然bar.h
をインクルードしているヘッダをインクルードすることもあり得るので、かなり厄介です。
「でも同じ名前で微妙に似ている定義ってそんなにあるか?」
Boost
のようなライブラリを使ったことがある人はご存じだと思いますが、そのような C++ 標準ライブラリを拡張しているライブラリは、名前が被っているクラスや関数が山ほどあります。しかも、拡張しているだけあって、定義もわざと似せている定義が多いです。
例えば、thread
、optional
、variant
はstd
にもboost
にも存在します。
これはstd
だけではなく、どの名前空間にも言えることで、やはり名前空間という大事な情報をusing namespace
でできるだけ省略しない方がいいと思います。
名前空間が多すぎて全部書くと長すぎる場合は、以下のように名前空間エイリアスを使いましょう。
namespace bf = boost::filesystem;
bf::path p;
それか、using
を使って、実際に使いたい名前だけ名前空間なしで使えるようにするのもいいですね。
using boost::filesystem::path;
path p;
でもこの場合は、実際に使いたいところだけに限定して、できるだけ小さいスコープにしましょう。
Discussion
別に闇でもなく、C++を作った本人監修のガイドラインにも以下のようにあります。
中を引用すると、
とあり、翻訳した内容は以下の通りです。
冗長で気を散らすので、実装コード上で衝突しないのであれば、使っていいと読めます。ただしヘッダオンリーライブラリなどでグローバルスコープに書くとかは弊害が大きいのでやめましょう。