引数依存のルックアップとは何かについての良い説明は何ですか?多くの人がそれをケーニッヒルックアップとも呼んでいます。
できれば知りたいのですが:
- なぜそれは良いことですか?
- なぜそれは悪いことですか?
- それはどのように機能しますか?
引数依存のルックアップとは何かについての良い説明は何ですか?多くの人がそれをケーニッヒルックアップとも呼んでいます。
できれば知りたいのですが:
Koenig Lookup、またはArgument Dependent Lookupは、C++のコンパイラによって非修飾名がどのように検索されるかを説明します。
C++11標準§3.4.2/1は次のように述べています。
関数呼び出し(5.2.2)のpostfix-expressionがunqualified-idの場合、通常の非修飾ルックアップ(3.4.1)で考慮されない他の名前空間が検索され、それらの名前空間では、名前空間スコープのフレンド関数宣言( 11.3)他の方法では見えないものが見つかる場合があります。検索に対するこれらの変更は、引数のタイプ(および、テンプレートテンプレート引数の場合は、テンプレート引数の名前空間)によって異なります。
簡単に言えば、ニコライ・ジョスティスは次のように述べています。
関数の名前空間で1つ以上の引数タイプが定義されている場合は、関数の名前空間を修飾する必要はありません。
簡単なコード例:
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass) {}
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
上記の例では、using
-declarationも-directiveもありませんが、コンパイラは、 Koenigルックアップを適用することにより、名前空間で宣言された関数としてusing
非修飾名を正しく識別します。doSomething()
MyNamespace
アルゴリズムは、ローカルスコープだけでなく、引数の型を含む名前空間も調べるようにコンパイラーに指示します。したがって、上記のコードでは、コンパイラはobj
、関数の引数であるオブジェクトdoSomething()
が名前空間に属していることを検出しますMyNamespace
。したがって、その名前空間を調べて、の宣言を見つけますdoSomething()
。
上記の簡単なコード例が示すように、ケーニッヒルックアップはプログラマーに便利さと使いやすさを提供します。Koenigルックアップがないと、完全修飾名を繰り返し指定するか、代わりに多数のusing
-declarationを使用するために、プログラマーにオーバーヘッドが発生します。
Koenigルックアップに過度に依存すると、セマンティックの問題が発生し、プログラマーが不意を突かれる場合があります。
std::swap
2つの値を交換するための標準ライブラリアルゴリズムであるの例を考えてみましょう。Koenigルックアップでは、次の理由から、このアルゴリズムを使用する際には注意が必要です。
std::swap(obj1,obj2);
次と同じ動作を示さない場合があります。
using std::swap;
swap(obj1, obj2);
ADLでは、どのバージョンのswap
関数が呼び出されるかは、渡される引数の名前空間によって異なります。
名前空間が存在し、、、が存在する場合A
、A::obj1
2番目の例では、への呼び出しが発生しますが、これはユーザーが望んでいたものではない可能性があります。A::obj2
A::swap()
A::swap()
さらに、何らかの理由でとが両方ともA::swap(A::MyClass&, A::MyClass&)
定義std::swap(A::MyClass&, A::MyClass&)
されている場合、最初の例は呼び出しますが、2番目の例はあいまいになるstd::swap(A::MyClass&, A::MyClass&)
ため、コンパイルされません。swap(obj1, obj2)
元AT&Tとベル研究所の研究者兼プログラマーであるAndrewKoenigによって考案されたためです。
標準C++03/11 [basic.lookup.argdep]:3.4.2引数依存の名前検索。
Koenig Lookupでは、名前空間を指定せずに関数を呼び出すと、引数の型が定義されている名前空間でも関数の名前が検索されます。これが、引数依存の名前ルックアップ、略して単にADLとしても知られている理由です。
Koenig Lookupのおかげで、次のように書くことができます。
std::cout << "Hello World!" << "\n";
それ以外の場合は、次のように記述する必要があります。
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
これは本当にタイピングが多すぎて、コードは本当に醜いように見えます!
つまり、Koenig Lookupがない場合、HelloWorldプログラムでさえ複雑に見えます。
たぶん、理由から始めて、それから方法に進むのが最善かもしれません。
名前空間が導入されたとき、別々のライブラリが互いに干渉しないように、すべてを名前空間で定義するというアイデアがありました。しかし、それはオペレーターに問題をもたらしました。たとえば、次のコードを見てください。
namespace N
{
class X {};
void f(X);
X& operator++(X&);
}
int main()
{
// define an object of type X
N::X x;
// apply f to it
N::f(x);
// apply operator++ to it
???
}
もちろん、あなたは書くことができN::operator++(x)
たでしょう、しかしそれは演算子のオーバーロードの全体のポイントを打ち負かしたでしょう。operator++(X&)
したがって、スコープ内にないという事実にもかかわらず、コンパイラーが検出できるようにするソリューションを見つける必要がありました。一方、operator++
呼び出しをあいまいにする可能性のある、別の無関係な名前空間で定義された別の名前空間を見つけることはできません(この単純な例では、あいまいさはありませんが、より複雑な例では、可能性があります)。解決策は引数依存ルックアップ(ADL)でした。ルックアップは引数(より正確には、引数のタイプ)に依存するため、このように呼び出されます。このスキームはAndrewR.Koenigによって発明されたため、Koenigルックアップとも呼ばれます。
秘訣は、関数呼び出しの場合、通常の名前ルックアップ(使用時にスコープ内の名前を検索する)に加えて、関数に指定された引数のタイプのスコープ内で2番目のルックアップが実行されることです。したがって、上記の例ではx++
、mainで記述するとoperator++
、グローバルスコープだけでなく、のタイプが定義されたスコープx
、N::X
つまり。でも検索されnamespace N
ます。そして、そこに一致するものが見つかるoperator++
ので、x++
正しく機能します。ただし、別operator++
の名前空間で定義されている別の名前空間はN2
見つかりません。f(x)
ADLは名前空間に制限されていないため、の代わりにをN::f(x)
使用することもできますmain()
。
私の意見では、それについてのすべてが良いわけではありません。コンパイラベンダーを含む人々は、時々不幸な振る舞いをするためにそれを侮辱してきました。
ADLは、C++11のfor-rangeループの大幅なオーバーホールを担当しています。ADLが意図しない影響を与えることがある理由を理解するには、引数が定義されている名前空間だけでなく、引数のテンプレート引数の引数、関数型のパラメーター型/それらの引数のポインター型のポインター型も考慮されることを考慮してください。 、などなど。
ブーストを使用した例
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
これにより、ユーザーがboost.rangeライブラリを使用する場合、両方std::begin
が検出され(ADLを使用してstd::vector
)、boost::begin
検出された( ADLを使用して)ため、あいまいさが生じましたboost::shared_ptr
。