yumetodoの旅とプログラミングとかの記録

旅や登山の記録やプログラミング関連の話とかフリーソフト紹介とか

なぜusing namespace std;を避けるべきか

もうすでにN人がN回言ってる有名な話なのですが改めて。

結論

よく分からないならusing namespace std;を使わない

using namespace std;とは

#include <iostream>
int main()
{
    std::cout << "arikitari_na_sekai" << std::endl;
}

こういうコードでstd::を書きたくないというときに

#include <iostream>
using namespace std;
int main()
{
    cout << "arikitari_na_sekai" << endl;
}

と書くことです。

太古の昔のC++には名前空間がなかった

はい、昔はなかったんです。どれくらい昔かというと、#include <iostream>ではなく#include <iostream.h>と書いていた時代ですね。

そのせいもあってか、名前空間を標準ライブラリ(STL)はすべてstd名前空間

圧倒的魔界C++の名前探索

関数に限っても、まず名前空間があり、ADL(実引数依存探索)があり、そのためのunqualified name look-upがあり、これにtemplateが加わって、さらに関数オーバーロードの複雑極まる規則が、とぱっと浮かぶ単語を並べるだけでも魔界です。

この仕様を把握しきれている人いるんですかねぇ(自分は無理)

競技プログラミング界隈での利用

競技プログラミング界隈は

  • 短く書く
  • シンプルで問題の制約上不要なエラーハンドリングは省く
  • 実行速度を速く
  • コード記述速度も速く

を求める世界です、少なくとも私にはそう見えます。

「短く書く」「コード記述速度も速く」の実現のために、using namespace std;が利用されることが多いです。

using namespaceが必須な場面

例えばC++14で追加された、標準ライブラリでのUDLsの利用では

#include <string>
void foo(const std::string& s) {
    //do something
}
int main(int argc, char* argv[])
{
    foo(std::string("argv[0]:") + argv[0]);
}

と書く代わりに

#include <string>
void foo(const std::string& s) {
    //do something
}
int main(int argc, char* argv[])
{
    using namespace std::literals;
    foo("argv[0]:"s + argv[0]);
}

のように書けます。この場合using namespaceは必須ですね。

問題点

名前が衝突してコンパイルが通らない

名前が衝突してかつ一意に定まらないとコンパイルエラーになります。

最も身近なのは、C++17でgcd(), lcm()が標準入りしたのですが、自前でそれを実装している人でしょうかね。

あとは @tancahn2380氏の

# include <iostream>
# include <vector>
# include <limits>
# include <algorithm>
# include <map>
# include <string>
# include <functional>
# include <cmath>
# include <iomanip>
using namespace std;

int main() {
    string a, b, c;
    cin >> a >> b >> c;
    transform(a.begin(), a.end(), a.begin(), toupper);
    transform(b.begin(), b.end(), b.begin(), toupper);
    transform(c.begin(), c.end(), c.begin(), toupper);
    cout << a[0] <<b[0] << c[0] << endl;
}

https://abc059.contest.atcoder.jp/submissions/1239801

とかでしょうか。

偶然名前解決に成功して全く違う処理が呼ばれる
可読性

複数のライブラリを使うときに、どの関数がどの名前空間にあるかわからないと、正しい関数を呼び出せているかわかりにくくなります。

using namespaceによって名前空間が省略されていると、それができないんですね。

いや、IntelliSenseあるだろという声が聞こえそうですが、いちいちマウスかざしてられっか!

一方で

#include <chrono>
#include <iostream>
void foo(){}
int main()
{
    const auto t1 = std::chrono::high_resolution_clock::now();
    foo();
    const auto t2 = std::chrono::high_resolution_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::miliseconds>(t2 - t1).cout() << std::endl;
}

のように名前空間が深いと辛いので

#include <chrono>
#include <iostream>
void foo(){}
int main()
{
    namespace ch = std::chrono;
    using clock = ch::high_resolution_clock;
    const auto t1 = clock::now();
    foo();
    const auto t2 = clock::now();
    std::cout << ch::duration_cast<ch::miliseconds>(t2 - t1).cout() << std::endl;
}

とかしてあげるといいと思います。

多分すべてのC++erが同意できるusing namespace std;を書いてはいけない場所

ヘッダーファイルのグローバル名前空間にて。

そのヘッダがincludeされるすべての翻訳単位にusing namespace std;を書くのと同じことなので(#includeはただの自動コピペマシーン)、イスラム教徒に豚肉を食べさせる並のテロです(自分はイスラム教徒ではないので実際のところは知らず)

競技プログラミング界隈の人とそれ以外の人でusing namespace std;を書いていいか揉める場所

ヘッダーファイル以外のグローバル名前空間にて。

つまり冒頭の

#include <iostream>
using namespace std;
int main()
{
    cout << "arikitari_na_sekai" << endl;
}

これ。

比較的多くのC++erが同意できるusing namespace std;を書いていい場所

関数の中。

つまり

#include <iostream>
int main()
{
    using namespace std;
    cout << "arikitari_na_sekai" << endl;
}

これ。

自分は可読性の観点からこれも避けたい過激派です。

なぜ競技プログラミング界隈の人とそれ以外のC++erが揉めるか

競技プログラミング界隈の人の主張
  • 何が悪いのかわからない
  • 名前衝突とかなったことない
  • 短くかけるのは正義
それ以外のC++erの主張
  • 名前衝突したことないとか一つのプログラムあたりの長さが短すぎるだけだろ
  • そもそも競技プログラミング界隈の人はクラス書いて継承したりとかファイル分割の仕方とかヘッダファイルの存在とか名前空間の概念とか理解してない人の方が多いんじゃないか
  • 一回書いたコードをメンテナンスしてみてから言いやがれ
  • 複数人開発してから出直して、どうぞ

個人的な思い

競技プログラミングでヘッダーファイル以外のグローバル名前空間にてusing namespace std;するのはまあ許せる

理由:

  • 競技プログラミングの哲学もわかる
  • 複数ファイルに別れることはない
  • 何百行に渡ることは少ない
  • メンテナンスしない
  • 複数人開発しない
競技プログラミング界隈の文化をよそに持ち込まないでくれ

競技プログラミングとそれ以外では何のためにコードを書くかからして違うわけで、何が最適かも変わるわけです。

つまり文化が違うわけで、文化の多様性を考慮するべきなんですね

競技プログラミングでもしないほうがいい

上に書いたように

# include <iostream>
# include <vector>
# include <limits>
# include <algorithm>
# include <map>
# include <string>
# include <functional>
# include <cmath>
# include <iomanip>
using namespace std;

int main() {
    string a, b, c;
    cin >> a >> b >> c;
    transform(a.begin(), a.end(), a.begin(), toupper);
    transform(b.begin(), b.end(), b.begin(), toupper);
    transform(c.begin(), c.end(), c.begin(), toupper);
    cout << a[0] <<b[0] << c[0] << endl;
}

のように死んでしまうので、自力で調べて解決できない人はやらないほうがいいと思います。

解決策

C++標準化委員会がmoduleを標準入りさせる

workaround

競技プログラミングとそれ以外の世界は別物なのでそれをわかった上でプログラミングする

閑話休題