web-dev-qa-db-ja.com

インライン名前空間とは何ですか?

C++ 11はinline namespacesを許可します。そのすべてのメンバーは、それを囲むnamespaceにも自動的に含まれます。私はこれの有用な応用を考えることができません-誰かがinline namespaceが必要であり、それが最も慣用的な解決策である状況の簡潔で簡潔な例を与えることができますか?

(また、namespaceがすべての宣言ではなく、1つのinlineで宣言されている場合、何が起こるかは明確ではありません。これは異なるファイルに存在する可能性があります。

308
Walter

インライン名前空間は、 symbol versioning に似たライブラリバージョン管理機能ですが、特定のバイナリ実行可能形式(つまり、クロスプラットフォーム)の機能ではなく、純粋にC++ 11レベル(つまり、クロスプラットフォーム)で実装されます。プラットフォーム固有)。

これは、ライブラリの作成者がネストされた名前空間を、すべての宣言が周囲の名前空間にあるかのように見せて動作させるメカニズムです(インライン名前空間はネストできるため、「よりネストされた」名前は最初の非-名前空間をインライン化し、それらの宣言が間にある名前空間のいずれかにあるかのように見え、動作します。

例として、vectorのSTL実装を検討してください。 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以前のバージョンで記述されていて、コンパイラをアップグレードするときにvectorのC++ 98バージョンが問題を引き起こしていることがわかった場合、「すべて」を行う必要がありますコードベースでstd::vectorへの参照を見つけて、std::pre_cxx_1997::vectorに置き換えます。

次の標準が来ると、STLベンダーはstd::vectorサポート(C++ 11を必要とする)でemplace_backの新しい名前空間を導入し、__cplusplus == 201103Lをインライン化するだけで手順を繰り返します。 。

それでは、なぜこのために新しい言語機能が必要なのですか?同じ効果を得るために、すでに次のことを実行できますか?

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ユーザーコードを考慮します(C++ 98のネームスペースstdに既に存在するテンプレートを完全に特殊化することが許可されています)。

// 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(のコピー)で見つかったものよりも効率的な実装を明らかに知っている型のセットのベクトルの独自の実装を提供する完全に有効なコードです。

But:テンプレートを特化するときは、宣言された名前空間で行う必要があります。標準では、vectorは名前空間stdであるため、ユーザーは当然、型を特殊化することを期待しています。

このコードは、バージョン管理されていない名前空間std、またはC++ 11インライン名前空間機能で機能しますが、using namespace <nested>を使用したバージョン管理のトリックでは機能しません。 vectorが定義されていたstdは直接ではありませんでした。

ネストされた名前空間を検出できる他の穴がありますが(下記のコメントを参照)、インライン名前空間はそれらすべてをプラグインします。そして、それだけです。将来的には非常に便利ですが、AFAIK the Standardは独自の標準ライブラリのインライン名前空間名を規定していません(ただし、これについては間違っていることが証明されます)。したがって、サードパーティのライブラリでのみ使用できます。標準そのもの(コンパイラベンダーが命名スキームに同意しない限り)。

322

http://www.stroustrup.com/C++11FAQ.html#inline-namespace (Bjarne Stroustrupによって作成および管理されているドキュメント。ほとんどのC++ 11機能。)

それによると、後方互換性のためにバージョン管理を許可することです。複数の内部名前空間を定義し、最新のものをinlineにします。とにかく、バージョニングを気にしない人のためのデフォルトです。私は、最新のものは、まだデフォルトではない将来のバージョンまたは最先端のバージョンである可能性があると思います。

例は次のとおりです。

// file V99.h:
inline namespace V99 {
    void f(int);    // does something better than the V98 version
    void f(double); // new feature
    // ...
}

// file V98.h:
namespace V98 {
    void f(int);    // does something
    // ...
}

// file Mine.h:
namespace Mine {
#include "V99.h"
#include "V98.h"
}

#include "Mine.h"
using namespace Mine;
// ...
V98::f(1);  // old version
V99::f(1);  // new version
f(1);       // default version

名前空間Mineusing namespace V99;を入れない理由はすぐにはわかりませんが、Bjarneの言葉を委員会の動機に取り入れるためにユースケースを完全に理解する必要はありません。

61
Steve Jessop

上記のすべての答えに加えて。

インライン名前空間を使用して、シンボル内のABI情報または関数のバージョンをエンコードできます。これは、ABIの下位互換性を提供するために使用されるためです。インライン名前空間を使用すると、リンカシンボル名のみに影響するため、APIを変更することなく、マングルされた名前(ABI)に情報を注入できます。

この例を考えてみましょう:

Fooというオブジェクトへの参照を取り、何も返さないbar関数を作成するとします。

Main.cppで言う

struct bar;
void Foo(bar& ref);

オブジェクトにコンパイルした後にこのファイルのシンボル名を確認する場合。

$ nm main.o
T__ Z1fooRK6bar 

リンカのシンボル名は異なる場合がありますが、関数名と引数型はどこかに確実にエンコードされます。

現在、barは次のように定義されている可能性があります。

struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};

ビルドタイプに応じて、barは同じリンカシンボルを持つ2つの異なるタイプ/レイアウトを参照できます。

このような動作を防ぐために、構造体barをインラインネームスペースにラップします。ここでは、ビルドタイプに応じて、barのリンカーシンボルが異なります。

したがって、次のように書くことができます。

#ifndef NDEBUG
inline namespace rel { 
#else
inline namespace dbg {
#endif
struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};
}

各オブジェクトのオブジェクトファイルを見ると、リリースを使用して1つをビルドし、デバッグフラグを使用してもう1つをビルドします。リンカシンボルにもインライン名前空間名が含まれていることがわかります。この場合

$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar

リンカーシンボル名は異なる場合があります。

シンボル名にrelおよびdbgが含まれていることに注意してください。

これで、デバッグをリリースモードまたはその逆にリンクしようとすると、ランタイムエラーとは反対にリンカーエラーが発生します。

2
coder3101

実際に、インライン名前空間の別の使用法を発見しました。

Qt を使用すると、 Q_ENUM_NS を使用して追加のニース機能が得られます。これには、Q_NAMESPACEで宣言されたメタオブジェクトが含まれるネームスペースが必要です。ただし、Q_ENUM_NSが機能するためには、同じファイルに対応するQ_NAMESPACEが必要です-¹-。また、存在できるのは1つだけであるか、重複定義エラーが発生します。これは、事実上、すべての列挙が同じヘッダーにある必要があることを意味します。うん.

または...インライン名前空間を使用できます。 inline namespaceで列挙を非表示にすると、メタオブジェクトの名前が異なるマングルになりますが、追加のネームスペースが存在しないようにユーザーを探します。

したがって、何らかの理由でそれを行う必要がある場合、すべてをlookが1つの名前空間のように見える複数のサブ名前空間に分割するのに役立ちます。もちろん、これは、using namespace innerを外側の名前空間に記述するのと似ていますが、 DRY 内側の名前空間の名前を2回記述する違反はありません。


  1. 実際にはそれよりも悪いです。同じブレースのセットにある必要があります。

  2. 完全に修飾せずにメタオブジェクトにアクセスしようとしない限り、メタオブジェクトは直接使用されることはほとんどありません。

0
Matthew