1. ホーム
  2. c++

[解決済み] インライン名前空間は何のためにあるのですか?

2022-03-14 11:53:24

質問

C++11では inline namespace のすべてのメンバは、自動的に周囲の namespace . これの有用な応用が思いつかないのですが、どなたか簡単で簡潔な例を挙げてください。 inline namespace が必要であり、それが最も慣用的な解決策である場合はどうでしょうか?

(また、(1)のような場合、どうなるのかがよくわかりません。 namespace が宣言されている場合 inline を、異なるファイルに存在するすべての宣言ではなく、1つの宣言に含めることができます。これはトラブルの元ではないでしょうか?)

解決方法は?

インライン名前空間は、ライブラリのバージョン管理機能で、次のようなものです。 シンボルバージョニング しかし、特定のバイナリ実行形式の機能(つまり、プラットフォーム固有)ではなく、純粋に C++11 レベル(つまり、クロスプラットフォーム)で実装されています。

これは、ライブラリの作者が、ネストされた名前空間を、その宣言がすべて周囲の名前空間にあるかのように見せ、動作させることができるメカニズムです(インライン名前空間はネストすることができるので、" more-nested" 名前は最初の非インライン名前空間まで浸透し、その宣言もその間のいずれかの名前空間にあるかのように見せ、動作させることができます)。

例として、STLの実装である vector . もし C++ の最初からインライン名前空間があったとしたら、C++98 ではヘッダの <vector> は、こんな感じになっていたかもしれません。

namespace std {

#if __cplusplus < 1997L // pre-standard C++
    inline
#endif

    namespace pre_cxx_1997 {
        template <class T> __vector_impl; // implementation class
        template <class T> // e.g. w/o allocator argument
        class vector : __vector_impl<T> { // private inheritance
            // ...
        };
    }
#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)
#  if __cplusplus == 1997L // C++98/03
    inline
#  endif

    namespace cxx_1997 {

        // std::vector now has an allocator argument
        template <class T, class Alloc=std::allocator<T> >
        class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
            // ...
        };

        // and vector<bool> is special:
        template <class Alloc=std::allocator<bool> >
        class vector<bool> {
            // ...
        };

    };

#endif // C++98/03 or later

} // namespace std

の値に応じて __cplusplus は、どちらか一方が vector の実装が選択されます。もし、あなたのコードベースがC++98以前の時代に書かれたもので、C++98バージョンの vector への参照を見つけるだけでよいのです。 std::vector に置き換えてください。 std::pre_cxx_1997::vector .

次の標準になると、STL ベンダーはまたこの手順を繰り返し、新しい名前空間を導入して std::vectoremplace_back をサポートし(これは C++11 が必要です)、もし __cplusplus == 201103L .

OK、では、なぜこのために新しい言語機能が必要なのでしょうか?すでに次のような方法で同じ効果を得ることができますよね?

namespace std {

    namespace pre_cxx_1997 {
        // ...
    }
#if __cplusplus < 1997L // pre-standard C++
    using namespace pre_cxx_1997;
#endif

#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)

    namespace cxx_1997 {
        // ...
    };
#  if __cplusplus == 1997L // C++98/03
    using namespace cxx_1997;
#  endif

#endif // C++98/03 or later

} // namespace std

の値に応じて __cplusplus のどちらか一方の実装を取得します。

そして、ほぼ正解でしょう。

次のようなC++98の有効なユーザーコードを考えてみましょう(名前空間に存在するテンプレートを完全に特殊化することが許されていました)。 std をC++98で既に使用しています)。

// I don't trust my STL vendor to do this optimisation, so force these 
// specializations myself:
namespace std {
    template <>
    class vector<MyType> : my_special_vector<MyType> {
        // ...
    };
    template <>
    class vector<MyOtherType> : my_special_vector<MyOtherType> {
        // ...
    };
    // ...etc...
} // namespace std

これは完全に妥当なコードで、ユーザはSTLにあるものより効率的な実装を知っているような、型の集合に対するベクターの独自の実装を提供します。

しかし : テンプレートを特殊化する場合は、そのテンプレートが宣言された名前空間で行う必要があります。スタンダードでは vector は名前空間 std そのため、ユーザは当然そこで型を特化することを期待します。

このコードはバージョン管理されていない名前空間で動作します。 std や、C++11 のインライン名前空間機能を使用したバージョン管理トリックでは使用できません。 using namespace <nested> という実装の詳細が公開されるからです。 vector が定義されていたのは std を直接表示します。

ネストした名前空間を検出するための穴は他にもありますが(以下のコメント参照)、インライン名前空間はそれらをすべて塞いでくれます。そして、それがすべてなのです。将来的には非常に有用だが、AFAIKでは標準ライブラリのインライン名前空間を規定していないため(これについては間違いが証明されると嬉しいが)、標準自体ではなくサードパーティのライブラリにしか使えない(コンパイラベンダが命名方式に合意しない限り)。