378

C ++ 11ではinline namespacesを使用でき、そのすべてのメンバーも自動的に囲んでいnamespaceます。私はこれの有用なアプリケーションを考えることができません-誰かinline namespaceが必要とされる状況とそれが最も慣用的な解決策である状況の簡潔で簡潔な例を教えてもらえますか?

namespace(また、aが1つの宣言で宣言されたときに何が起こるかはわかりませんが、inlineすべての宣言ではなく、異なるファイルに存在する可能性があります。これは問題を引き起こしていませんか?)

4

6 に答える 6

371

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

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

例として、の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::vector付きの新しい名前空間emplace_back(C ++ 11が必要)を導入し、その1つのiffをインライン化し__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ユーザーコードを検討してください(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>たという実装の詳細を公開するためです。vectorstd

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

于 2012-06-13T15:35:00.963 に答える
80

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

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

于 2012-06-13T13:52:53.337 に答える
21

他のすべての答えに加えて。

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

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

Fooオブジェクトsayへの参照を取り、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

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

于 2019-06-21T01:11:23.773 に答える
5

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

Qtを使用すると、を使用していくつかの優れた機能を利用できます。これQ_ENUM_NSには、囲んでいる名前空間に、で宣言されたメタオブジェクトが必要ですQ_NAMESPACE。ただし、機能するには、同じファイル⁽¹⁾Q_ENUM_NSに対応するものが存在する必要があります。また、存在できるのは1つだけです。そうしないと、重複した定義エラーが発生します。これは、事実上、すべての列挙が同じヘッダーにある必要があることを意味します。うん。Q_NAMESPACE

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

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


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

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

于 2019-08-27T20:24:45.500 に答える
4

したがって、要点を要約するusing namespace v99と、同じではありませんでしたが、前者は、同じバージョン管理機能を提供しながらinline namespace、使用の問題を修正する専用キーワード(インライン)がC ++ 11に導入される前の、バージョンライブラリの回避策でした。using以前はADLで問題を引き起こしてusing namespaceいましたが(ADLは現在using、ディレクティブに従っているように見えます)、ユーザーによるライブラリクラス/関数などのオフライン特殊化は、真の名前空間(名前はユーザーは知らない、または知らないはずです。つまり、スペシャライゼーションを解決するには、B::だけでなくB:: abi_v2::を使用する必要があります)。

//library code
namespace B { //library name the user knows
    namespace A { //ABI version the user doesn't know about
        template<class T> class myclass{int a;};
    }
    using namespace A; //pre inline-namespace versioning trick
} 

// user code
namespace B { //user thinks the library uses this namespace
    template<> class myclass<int> {};
}

これにより、静的分析の警告が表示されますfirst declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]。ただし、名前空間Aをインラインにすると、コンパイラーは特殊化を正しく解決します。ただし、C ++ 11拡張機能を使用すると、問題は解消されます。

using;を使用すると、アウトオブライン定義は解決されません。ネストされた/ネストされていない拡張名前空間ブロックで宣言する必要があります(つまり、何らかの理由で関数の独自の実装を提供することが許可されている場合、ユーザーはABIバージョンを再度知る必要があります)。

#include <iostream>
namespace A {
    namespace B{
        int a;
        int func(int a);
        template<class T> class myclass{int a;};
        class C;
        extern int d;
    } 
    using namespace B;
} 
int A::d = 3; //No member named 'd' in namespace A
class A::C {int a;}; //no class named 'C' in namespace 'A' 
template<> class A::myclass<int> {}; // works; specialisation is not an out-of-line definition of a declaration
int A::func(int a){return a;}; //out-of-line definition of 'func' does not match any declaration in namespace 'A'
namespace A { int func(int a){return a;};} //works
int main() {
    A::a =1; // works; not an out-of-line definition
}

Bをインラインにすると、この問題は解消されます。

名前空間が持つ他の機能inlineは、ライブラリライターがライブラリに透過的な更新を提供できるようにすることです。1)ユーザーに新しい名前空間名でコードをリファクタリングすることを強制せずに、2)冗長性の欠如を防ぎ、3)APIに関係のない詳細の抽象化を提供します。 4)非インライン名前空間を使用する場合と同じ有益なリンカー診断と動作を提供します。ライブラリを使用しているとしましょう。

namespace library {
    inline namespace abi_v1 {
        class foo {
        } 
    }
}

これにより、ユーザーlibrary::fooはABIバージョンを知らなくても、またはドキュメントにABIバージョンを含めることなく電話をかけることができ、見た目もすっきりします。使用library::abiverison129389123::fooすると汚れて見えます。

更新が行われた場合foo、つまりクラスに新しいメンバーを追加した場合、既存のプログラムはまだメンバーを使用しておらず、インライン名前空間名を変更してもAPIレベルでは何も変更されないため、APIレベルの既存のプログラムには影響しません。library::fooまだ動作するため。

namespace library {
    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

ただし、それにリンクするプログラムの場合、インライン名前空間名は通常の名前空間のようにシンボル名にマングルされるため、変更はリンカーに対して透過的ではありません。したがって、アプリケーションが再コンパイルされていないが、ライブラリの新しいバージョンとリンクされているabi_v1場合、実際にリンクしてABIの非互換性のために実行時に不思議な論理エラーを引き起こすのではなく、シンボルが見つからないというエラーが表示されます。新しいメンバーを追加すると、コンパイル時(APIレベル)でプログラムに影響を与えない場合でも、型定義の変更によりABI互換性が発生します。

このシナリオでは:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

abi_v12つの非インライン名前空間を使用する場合と同様に、グローバルシンボルの1つでマングルされ、正しい(古い)型定義を使用するため、アプリケーションを再コンパイルすることなく、新しいバージョンのライブラリをリンクできます。ただし、アプリケーションを再コンパイルすると、参照はに解決されlibrary::abi_v2ます。

使用using namespaceは使用よりも機能的ではinlineありませんが(行外の定義は解決されません)、上記と同じ4つの利点があります。しかし、本当の問題は、それを行うための専用のキーワードがあるのに、なぜ回避策を使い続けるのかということです。より適切な方法で、冗長性を減らし(2行ではなく1行のコードを変更する必要があります)、意図を明確にします。

于 2020-03-28T13:50:44.367 に答える
1

インライン名前空間を使用して、名前空間内の機能/名前へのきめ細かいアクセスを提供することもできます。

これはで使用されstd::literalsます。literalsstdの名前空間はすべてインライン名前空間であるため、次のようになります。

  • どこかで使用するusing namespace std;と、std内のすべてのユーザー定義リテラルにもアクセスできます。
  • ただし、ローカルコードにudlのセットが必要な場合は、それも可能であり、その名前空間で定義されたudlシンボルを取得using namespace std::literals::string_literals;するだけです。

これは、修飾されていない(udl、演算子など)にアクセスしたいシンボルに対して便利な手法のようです。インラインの名前空間にバンドルするだけで、代わりにその(サブ)名前空間で特定の使用を行うことができます。ライブラリ全体の名前空間の。

于 2022-02-02T08:01:10.120 に答える