Как перегрузка операторов arrow-> работает внутри С++?
Я понимаю нормальную перегрузку оператора. Компилятор может напрямую перевести их на вызов метода. Я не очень разбираюсь в операторе → . Я писал свой первый пользовательский итератор, и я чувствовал потребность в операторе → . Я взглянул на исходный код stl и внедрил свой собственный:
MyClass* MyClassIterator::operator->() const
{
//m_iterator is a map<int, MyClass>::iterator in my code.
return &(m_iterator->second);
}
Затем я могу использовать экземпляр MyClassIterator, например:
myClassIterator->APublicMethodInMyClass().
Похоже, компилятор делает два шага здесь.
1. Вызовите метод → (), чтобы получить временную переменную MyClass *.
2. Вызовите APublicMethodInMyClass для переменной temp с помощью оператора → .
Правильно ли я понимаю?
Ответы
Ответ 1
myClassIterator->APublicMethodInMyClass()
- не что иное, как следующее:
myClassIterator.operator->()->APublicMethodInMyClass()
Первый вызов перегруженного operator->
получает указатель некоторого типа, который имеет доступную (от вашего сайта) функцию-член, которая называется APublicMethodInMyClass()
. Обычные правила поиска функций следуют для разрешения APublicMethodInMyClass()
, конечно, в зависимости от того, является ли это виртуальным или нет.
Существует необязательная временная переменная; компилятор может или не может скопировать указатель, возвращенный &(m_iterator->second)
. По всей вероятности, это будет оптимизировано. Однако временные объекты типа MyClass
будут созданы.
Обычные оговорки также применимы к m_iterator
- убедитесь, что ваши вызовы не имеют доступа к недействительному итератору (т.е. если вы используете vector
, например).
Ответ 2
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*