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


334

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

(また、a namespaceinline1つの宣言で宣言されたが、すべての宣言ではなかった場合に何が起こるかは明確ではありません。これは、異なるファイルに存在する可能性があります。これは問題を起こしていませんか?)

回答:


339

インライン名前空間は、シンボルバージョン管理に似たライブラリバージョン管理機能ですが、特定のバイナリ実行可能形式(つまり、プラットフォーム固有)の機能ではなく、純粋に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::vectoremplace_backサポート付きの新しい名前空間を導入し(C ++ 11が必要)、その1つの差分をインライン化し__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直接ではなかった実装の詳細を公開するためです。

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


23
+1はusing namespace V99;、Stroustrupの例で機能しない理由を説明します。
スティーブジェソップ

3
同様に、まったく新しいC ++ 21実装をゼロから始めた場合、古いナンセンスをたくさん実装することに負担をかけたくありませんstd::cxx_11。すべてのコンパイラーが常に標準ライブラリのすべての古いバージョンを実装するわけではありませんが、既存の実装が新しいものを追加するときに古いものを残すことを要求するのは非常にわずかな負担であると考えがちですが、実際にはすべてとにかく。私は、標準が有効に実行できたことをオプションにしたと思いますが、標準名がある場合はそれを使用します。
スティーブジェソップ

46
それだけではありません。ADLも理由であり(ADLはディレクティブの使用に追随しません)、名前の検索も行いました。(using namespace A名前空間Bでは、名前空間Bで名前を検索すると、名前空間Aで名前が非表示になります(B::nameインライン名前空間ではそうではありません)。
Johannes Schaub-litb

4
ifdef完全なベクトル実装にsを使用しないのはなぜですか?すべての実装は1つの名前空間にありますが、前処理後に定義されるのはそのうちの1つだけです
sasha.sochka

6
@ sasha.sochka。この場合、他の実装は使用できません。それらはプリプロセッサによって削除されます。インライン名前空間を使用すると、完全修飾名(またはusingキーワード)を指定することにより、任意の実装を使用できます。
Vasily Biryukov 2013年

70

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の言葉を理解するために、ユースケースを完全に理解する必要はありません。


実際、最後のf(1)バージョンはインラインV99名前空間から呼び出されますか?
Eitan T

1
@EitanT:はい、グローバル名前空間にはusing namespace Mine;があり、Mine名前空間にはインライン名前空間のすべてが含まれているためMine::V99です。
スティーブジェソップ2013年

2
@ウォルター:を含むリリースでinlineファイルから削除します。もちろん、同時に修正して、追加のインクルードを追加することもできます。ライブラリの一部であり、クライアントコードの一部ではありません。V99.hV100.hMine.hMine.h
スティーブジェソップ

5
@ウォルター:彼らはインストールしていないV100.h、彼らは「マイン」と呼ばれるライブラリをインストールしています。- 「鉱山」のバージョン99で3つのヘッダファイルがありMine.hV98.hそしてV99.h。- 「鉱山」のバージョン100で4つのヘッダファイルがありMine.hV98.hV99.hとはV100.h。ヘッダーファイルの配置は、ユーザーには関係のない実装の詳細です。Mine::V98::fコードの一部またはすべてから特別に使用する必要があることを意味する互換性の問題を発見した場合、Mine::V98::f古いコードからの呼び出しとMine::f新しく作成されたコードへの呼び出しを混在させることができます。
スティーブジェソップ

2
@Walter他の回答が言及しているように、テンプレートは、宣言されている名前空間を使用する名前空間ではなく、テンプレートが宣言されている名前空間に特化する必要があります。MineMine::V99またはに特化する必要はありませんMine::V98
ジャスティン時間-モニカを

8

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

インライン名前空間は、シンボル内の関数の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

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

存在の通知 reldbgシンボル名インチ

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


1
はい、それは理にかなっています。したがって、これはライブラリの実装者などにとってはより多くのことです。
Walter、

3

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

ではQtの、あなたには、いくつかの余分なを取得し、素敵では使用していますQ_ENUM_NS順番に囲まれた名前空間を使用して宣言されたメタオブジェクトを、持っていることを必要としますQ_NAMESPACE。ただし、が機能するためQ_ENUM_NSQ_NAMESPACE は、同じファイルに対応するthereが必要です。そして、1つしか存在しないか、定義の重複エラーが発生します。これは、事実上、すべての列挙が同じヘッダーにある必要があることを意味します。ああ。

または...インライン名前空間を使用できます。列挙型をで非表示にするinline namespaceと、メタオブジェクトにさまざまなマングル名が付けられますが、追加の名前空間が存在しないようにユーザーに見えます²。

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


  1. それは実際にはそれよりも悪いです。同じ中括弧のセットでなければなりません。

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


コードのスケルトンでそれをスケッチできますか?(理想的にはQtへの明示的な参照なし)。それはすべて、かなり複雑/不明瞭に聞こえます。
Walter

そうではない...簡単に。個別の名前空間が必要な理由は、Qt実装の詳細に関係しています。TBH、同じ要件を持つQt以外の状況を想像するのは困難です。ただし、このQt固有のシナリオでは、非常に便利です。例については、gist.github.com / mwoehlke -kitware / またはgithub.com/Kitware/seal-tk/pull/45をご覧ください。
マシュー

0

したがって、主なポイントをまとめるusing namespace v99inline namespace、同じではありませんでしたが、前者はC ++ 11で専用のキーワード(インライン)が導入される前のバージョンライブラリの回避策usingであり、同じバージョン管理機能を提供しながら、の使用に関する問題を修正しました。をusing namespace使用すると、ADLで問題が発生します(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_v1ABIの非互換性が原因で実際​​にリンクされて実行時に不可解なロジックエラーが発生するのではなく、シンボルが見つかりませんというエラーが表示されます。新しいメンバーを追加すると、コンパイル時にプログラム(APIレベル)に影響しなくても、型定義の変更によりABI互換性が発生します。

このシナリオでは:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.