最も過小評価されている C++ 機能の 1 つ:名前空間エイリアス

約 2 か月前に、次の r/cpp コメントを書きました:

スレッドでは、新しいライブラリが提示されました。1 人のユーザーが長いネームスペース名について不満を述べ、上記の回答を得ました。賛成票の数から判断すると、人々は私のコメントに同意しているように見えました.このブログ投稿では、それについて詳しく説明します.

でもその前に、私の話を少しさせてください。

名前付けについて

私はライブラリの作者です。

そのため、私は他の人が書いたコードに統合されるコードを書きます。私が書いた関数とクラスは、私のユーザーが書いた関数とクラスと共存します。>名前 は異なります。したがって、どのとも競合しないようにシンボルに名前を付ける必要があります 他の可能なシンボル名;コードが最終的にどこに行き着くのか分からないので、名前すら知りません。

幸いなことに、この問題は C++ やその他のほとんどの高水準言語で非常に簡単に解決できます。それらは、物事を 名前空間 に入れる方法を提供します。 名前の衝突を避けるためです。その後、名前空間名 (多くの場合、ライブラリ名自体) を考え出すだけでよく、すべてをその名前空間に配置すれば、名前の問題について二度と心配する必要はありません!

それが理論です。実際には、私たち、または少なくとも私は、CS の基本的な問題の 1 つに遭遇します。

私はライブラリの作者です。

私はひどい

私の弁明:名前を付けるのは難しい 名前はかなり短く、プロジェクトのスコープ - 名前空間 (!) - で一意である必要があります。また、直感的でわかりやすい名前にする必要があります。きれいな名前もいいでしょう (きれいという意味ではありません) names_like_this 醜い NamesLikeThat とは対照的に 、一般的には「かわいい」という意味です)。

常に覚えておいてください:ユーザーはあなたの名前を簡単に変更できないため、入力したい名前を選択してください。 ロットを入力します .

名前を付ける必要があるものの 1 つは、もちろんライブラリ自体です。いい頭字語 (SFML、POCO など) や、創造的でやや適切な言葉 (Boost、Catch など) を考え出すのに何時間も費やす代わりに、私は目的に合わせて名前を付けるだけです。

文字列識別子クラスを提供する私のライブラリは string_id と名付けられ、メモリ アロケータ クラスを提供する私のライブラリは memory と名付けられました。ゲーム エンジンを書いたことがあるとしたら、それはまさにその名前になるでしょう - ゲーム エンジン .

これは個々のBoostライブラリに似ており、ライブラリが何であるかを直接情報を提供すると主張することができますが、私のものはスタンドアロンであり、より大きなものにバンドルされていません.この命名戦略のその他の (欠点) 利点は別として、非常に基本的な問題に遭遇します:名前空間名です。

たとえば、メモリ はかなり一般的な名前です。特に名前空間の場合です。github 検索では、約 15,000 のコードが返されます。 最上位の名前空間が問題を引き起こしているためです。

代わりに、私のユニバーサル (そして Github) ユーザー名 - foonathan を使用します。 - 最上位の名前空間であり、ライブラリ名はネストされた名前空間です。したがって、シンボルにアクセスするには foonathan::library::foo と書く必要があります library::foo の代わりに .

これにより、私は次のガイドラインに従います。

ガイドライン I:名前空間名は一意にする必要があります

ライブラリを作成している場合、最上位の名前空間名は、グローバル スコープにエクスポートされる唯一のシンボルである必要があります。

グローバル スコープは、まあ、グローバルです。すべての間で共有されます 、使用するすべてのライブラリ、独自のコード。すでに C ライブラリによって汚染されているため、混雑することができます

したがって、名前の衝突を避けるようにすることが特に重要です。つまり、グローバル スコープに入れるすべてのもの、つまりトップレベルの名前空間です。 - 一意である必要があります。

どのようにして一意性を「保証」しますか?私は 2 つの戦略を特定しました:

戦略 a):長く表現力豊かな名前空間を使用する

名前に入れる情報が多ければ多いほど、名前が衝突する可能性は低くなります。したがって、できる限りすべてを名前空間に入れます。

戦略 b):あなた/あなたの組織 (Github-) 名を使用する

Github で公開するコードを書いている場合、ユーザー名または組織名という一意の名前が既に存在します。Github は非常に多くの人に使用されているため、名前もグローバルに一意になる可能性があります。

したがって、それを名前空間に入れるだけです。最上位の名前空間 (私のように) またはプレフィックスとして。

命名について (続き)

私はライブラリの作者です。

私はナルシストではありません。コードを使用するたびに私の名前を入力する必要はありません。

ありがたいことに、その必要はありません。

しばしば忘れられ、Google の検索結果の数から判断すると、あまり話題に上らない名前空間に関する小さな C++ 機能があります:名前空間のエイリアスです。

あなたが知らなかった場合 (おそらくそうです):名前空間エイリアスは、名前空間のエイリアスです。そうすれば、古い名前を使用する場所ならどこでも (ほぼ) 代わりに新しい名前を使用できます。

次のようになります:

// a namespace alias
namespace NewName = OldName;

たとえば、string_id では すべての前に foonathan::string_id:: を付ける代わりにライブラリ 、書き込み:

namespace sid = foonathan::string_id;
// now you can access it through sid::

そして思い出のために このようなエイリアスはデフォルトで有効になっています:memory:: と書くだけです。 、 foonathan::memory:: へのエイリアスのおかげで、トップレベルの名前空間を除外します .最上位の名前空間名 memory を使用できない場合 、エイリアスを無効にする CMake オプションがあります。

更新:ビルド システム内で問題が発生するため、このオプションを削除しました。エイリアスを提供する特別なヘッダーを含める必要があります。

これは次のガイドラインに直接つながります。

ガイドライン II:使用する名前空間のエイリアス

そのため、すべてのライブラリが私のガイドライン I に従っているため、すべてのライブラリには長く醜い名前空間名が付いています。

何をしますか?簡単:名前空間を短い名前にエイリアスします。エイリアスは ローカル である必要があるだけです。 ユニーク - あなたのプロジェクトでは、つまり、誰もが好むと思われる派手な 3 文字の略語を使用できます。

それに基づいて、ライブラリの作成者として、ユーザーの生活を楽にすることができます。 #ifndef ユーザーが衝突しない限り、問題なく使用できます。衝突が発生した場合、エイリアスは単純に #define にできます。

これは、これらの目的を犠牲にすることなく、ユニークで美しい名前の間の適切な妥協点です.

インライン名前空間

名前空間に関連するあいまいな機能についてはすでに話しているので、inline namespace について言及する必要があります。 同じように。これは C++11 で追加されたもので、基本的にすべてのシンボルを親スコープに自動的にエクスポートする名前空間です。

です!近い将来、それらの使用例についてさらに詳しく書く予定です。ここでは、バージョン処理という 1 つの使用法だけを考えてみましょう。 fancy_allocator があるとしましょう クラス。

namespace my_long_unique_lib_name // Guideline I
{
 inline namespace v1
 {
 class fancy_allocator
 {
 // fancy allocation stuff
 };
 }
}

v1以降 inline です 名前空間であり、コンパイラに対して透過的であり、クライアント コードは次のように記述できます:

namespace lul = my_long_unique_lib_name; // Guideline II
...
lul::fancy_allocator alloc;

時が経ち、誰かが fancy_allocator さらに派手にすることもできます.しかし、これは悲しいことにデフォルトのコンストラクタを削除します!したがって、削除する代わりに、新しいバージョンが古いバージョンと一緒に追加されます:

namespace my_long_unique_lib_name // Guideline I
{
 namespace v1
 {
 class fancy_allocator
 {
 // fancy allocation stuff
 };
 }
 inline namespace v2
 {
 class fancy_allocator
 {
 // even fancier allocation stuff
 };
 }
}

現在 v2 inline です 、デフォルトでは最先端のものだけが必要なので.

しかし、上記のクライアント コードはコンパイルされません! 誰かがデフォルトで構築された fancy_allocator を使用してすべてのコードをリファクタリングする必要があります。 幸いなことに、名前空間エイリアスのおかげで、これは簡単に修正できます:

namespace lul = my_long_unique_lib_name::v1; // only change!
...
lul::fancy_allocator alloc;

すべてのアクセスはエイリアスを介して行われるため、エイリアスのみを変更して v1 を含める必要があります。 残りはそのままにしておくことができます。

コードのリファクタリングは後で行うことができます。この手法は、Boost のベスト プラクティス ハンドブックでも推奨されています。

結論

TL;DR:名前空間エイリアスは素晴らしい!

特に、ライブラリの作成者は、非常に長くて醜いものになるという代償を払っても、一意のトップレベルの名前空間名を使用するため、ユーザーは エイリアス を使用できます。 名前を短くてきれいな名前にします。

デフォルトのエイリアス (マクロで無効にできる) もライブラリで提供できます。これにより、99% のユーザーに短くてきれいな名前空間名を提供し、残りの 1% にもう少し多くの作業をさせることができます。

すべてのアクセスがネームスペース エイリアスを介して行われる場合、ユーザーは inline namespace の変更にも簡単に適応できます。;エイリアスを変更するだけです。