通常のオペレーターの過負荷を理解しています。コンパイラはそれらをメソッド呼び出しに直接変換できます。 ->演算子についてはあまり明確ではありません。私は最初のカスタムイテレータを作成していて、->演算子が必要だと感じました。私はstlのソースコードを見て、自分でそれを実装しました:
MyClass* MyClassIterator::operator->() const
{
//m_iterator is a map<int, MyClass>::iterator in my code.
return &(m_iterator->second);
}
次に、次のようにMyClassIteratorのインスタンスを使用できます。
myClassIterator->APublicMethodInMyClass().
コンパイラはここで2つのステップを実行するように見えます。 1.->()メソッドを呼び出して、一時的なMyClass *変数を取得します。 2.一時変数でAPublicMethodInMyClassを呼び出し、その->演算子を使用します。
私の理解は正しいですか?
_myClassIterator->APublicMethodInMyClass()
_
以下にすぎません:
_myClassIterator.operator->()->APublicMethodInMyClass()
_
オーバーロードされた_operator->
_への最初の呼び出しは、APublicMethodInMyClass()
と呼ばれる(呼び出しサイトから)アクセス可能なメンバー関数を持つ何らかの型のポインターを取得します。もちろん、それが仮想かどうかに応じて、APublicMethodInMyClass()
を解決するために通常の関数ルックアップルールに従います。
一時変数は必ずしも必要ではありません。コンパイラはポインタをコピーする場合としない場合があります&(m_iterator->second)
によって返されます。おそらく、これは最適化されます。ただし、タイプMyClass
の一時オブジェクトは作成されません。
通常の注意事項は_m_iterator
_にも適用されます-呼び出しが無効化されたイテレータにアクセスしないようにしてください(つまり、vector
を使用している場合など)。
_operator->
_は、言語に特別なセマンティクスがあり、オーバーロードすると、結果に再適用されます。残りの演算子は一度だけ適用されますが、_operator->
_は、生のポインターに到達するために必要な回数だけコンパイラーによって適用され、そのポインターによって参照されるメモリにアクセスするためにもう一度適用されます。
_struct A { void foo(); };
struct B { A* operator->(); };
struct C { B operator->(); };
struct D { C operator->(); };
int main() {
D d;
d->foo();
}
_
前の例では、式d->foo()
で、コンパイラはオブジェクトd
を取得し、_operator->
_を適用して、C
タイプのオブジェクトを生成します。次に、演算子を再適用してB
のインスタンスを取得し、再適用して_A*
_に到達します。その後、オブジェクトを間接参照し、ポイントされたデータに到達します。
_d->foo();
// expands to:
// (*d.operator->().operator->().operator->()).foo();
// D C B A*
_