回答:
インライン名前空間は、シンボルバージョン管理に似たライブラリバージョン管理機能ですが、特定のバイナリ実行可能形式(つまり、プラットフォーム固有)の機能ではなく、純粋に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つの差分をインライン化し__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
直接ではなかった実装の詳細を公開するためです。
ネストされた名前空間を検出できる穴は他にもあります(下記のコメントを参照)が、インライン名前空間はそれらすべてをプラグインします。これですべてです。将来的には非常に便利ですが、標準は独自の標準ライブラリのインライン名前空間名を規定していません(ただし、これについて間違っていることが証明されたいと思います)。このため、サードパーティのライブラリにのみ使用できます。標準自体(コンパイラのベンダーが命名方式に同意しない限り)。
std::cxx_11
。すべてのコンパイラーが常に標準ライブラリのすべての古いバージョンを実装するわけではありませんが、既存の実装が新しいものを追加するときに古いものを残すことを要求するのは非常にわずかな負担であると考えがちですが、実際にはすべてとにかく。私は、標準が有効に実行できたことをオプションにしたと思いますが、標準名がある場合はそれを使用します。
using namespace A
名前空間Bでは、名前空間Bで名前を検索すると、名前空間Aで名前が非表示になります(B::name
インライン名前空間ではそうではありません)。
ifdef
完全なベクトル実装にsを使用しないのはなぜですか?すべての実装は1つの名前空間にありますが、前処理後に定義されるのはそのうちの1つだけです
using
キーワード)を指定することにより、任意の実装を使用できます。
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
名前空間から呼び出されますか?
using namespace Mine;
があり、Mine
名前空間にはインライン名前空間のすべてが含まれているためMine::V99
です。
inline
ファイルから削除します。もちろん、同時に修正して、追加のインクルードを追加することもできます。ライブラリの一部であり、クライアントコードの一部ではありません。V99.h
V100.h
Mine.h
Mine.h
V100.h
、彼らは「マイン」と呼ばれるライブラリをインストールしています。- 「鉱山」のバージョン99で3つのヘッダファイルがありMine.h
、V98.h
そしてV99.h
。- 「鉱山」のバージョン100で4つのヘッダファイルがありMine.h
、V98.h
、V99.h
とはV100.h
。ヘッダーファイルの配置は、ユーザーには関係のない実装の詳細です。Mine::V98::f
コードの一部またはすべてから特別に使用する必要があることを意味する互換性の問題を発見した場合、Mine::V98::f
古いコードからの呼び出しとMine::f
新しく作成されたコードへの呼び出しを混在させることができます。
Mine
、Mine::V99
またはに特化する必要はありませんMine::V98
。
他のすべての答えに加えて。
インライン名前空間は、シンボル内の関数の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
シンボル名インチ
デバッグをリリースモードまたはその逆にリンクしようとすると、ランタイムエラーとは逆に、リンカーエラーが発生します。
実際、インライン名前空間の別の使用法を発見しました。
ではQtの、あなたには、いくつかの余分なを取得し、素敵では使用していますQ_ENUM_NS
順番に囲まれた名前空間を使用して宣言されたメタオブジェクトを、持っていることを必要としますQ_NAMESPACE
。ただし、が機能するためQ_ENUM_NS
にQ_NAMESPACE
は、同じファイルに対応するthereが必要です。そして、1つしか存在しないか、定義の重複エラーが発生します。これは、事実上、すべての列挙が同じヘッダーにある必要があることを意味します。ああ。
または...インライン名前空間を使用できます。列挙型をで非表示にするinline namespace
と、メタオブジェクトにさまざまなマングル名が付けられますが、追加の名前空間が存在しないようにユーザーに見えます²。
したがって、何らかの理由で必要な場合に、すべてを 1つの名前空間のように見える複数のサブ名前空間に分割するのに役立ちます。もちろん、これはusing namespace inner
外部の名前空間に書き込むのと似ていますが、内部の名前空間の名前を2回書き込むというDRY違反はありません。
それは実際にはそれよりも悪いです。同じ中括弧のセットでなければなりません。
完全修飾せずにメタオブジェクトにアクセスしようとしない限り、メタオブジェクトが直接使用されることはほとんどありません。
したがって、主なポイントをまとめるusing namespace v99
とinline 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_v1
ABIの非互換性が原因で実際にリンクされて実行時に不可解なロジックエラーが発生するのではなく、シンボルが見つかりませんというエラーが表示されます。新しいメンバーを追加すると、コンパイル時にプログラム(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行のコードを変更する必要があります)、意図が明確になります。
using namespace V99;
、Stroustrupの例で機能しない理由を説明します。