Перегрузка операторов доступа к членам ->,. *

130

Я понимаю , большинство перегрузки операторов, за исключением операторов доступа члена ->, .*, и ->*т.д.

В частности, что передается этим операторным функциям, а что нужно возвращать?

Как операторная функция (например operator->(...)) узнает, на какой член ссылается? Это может знать? Это вообще нужно знать?

Наконец, нужно ли учитывать какие-либо константы? Например, при перегрузке чего-то вроде operator[]обычно вам потребуются как константная, так и неконстантная версия. Требуются ли для операторов доступа к членам константные и неконстантные версии?

бинго
источник
1
Я считаю, что приведенный выше C ++ - Faq затрагивает все вопросы, заданные в предыдущем Q.
Alok Save
constи не constверсии operator->не требуются , но предоставление обоих может быть полезным.
Fred Foo
1
См. Также: yosefk.com/c++fqa/operator.html
Дьердь Андрасек 08
9
@Als: В FAQ не поясняется, как перегружать ->*и .*. Фактически, он даже не упоминает о них! Я считаю, что они редко попадают в FAQ, но я с удовольствием добавлю ссылку на этот вопрос из FAQ. Пожалуйста, не закрывайте это как обман FAQ!
sbi
@sbi, мне просто не удалось найти ссылку на этот вопрос в вашем (потрясающем) FAQ, и в итоге я задал повторяющийся вопрос. Не могли бы вы сделать это более очевидным? (извиняюсь, если это уже очевидно).
P i

Ответы:

145

->

Это единственная действительно сложная задача. Это должна быть нестатическая функция-член, и она не принимает аргументов. Возвращаемое значение используется для поиска членов.

Если возвращаемое значение является другим объектом типа класса, а не указателем, то последующий поиск члена также обрабатывается operator->функцией. Это называется «детализированным поведением». Язык объединяет operator->вызовы в цепочку, пока последний из них не вернет указатель.

struct client
    { int a; };

struct proxy {
    client *target;
    client *operator->() const
        { return target; }
};

struct proxy2 {
    proxy *target;
    proxy &operator->() const
        { return * target; }
};

void f() {
    client x = { 3 };
    proxy y = { & x };
    proxy2 z = { & y };

    std::cout << x.a << y->a << z->a; // print "333"
}

->*

Этот сложен только в том, что в нем нет ничего особенного. Не перегруженный версия требует объект указателя на тип класса на левой стороне и объект указателя типа элемента справа. Но когда вы его перегружаете, вы можете принимать любые аргументы и возвращать все, что захотите. Это даже не обязательно должен быть нестатический член.

Другими словами, это один просто нормальный бинарный оператор , как +, -, и /. См. Также: Свободный оператор -> * перегружает зло?

.* и .

Их нельзя перегрузить. Когда левая часть относится к типу класса, уже есть встроенное значение. Возможно, имело бы смысл определить их для указателя с левой стороны, но комитет по разработке языков решил, что это скорее запутает, чем полезно.

Перегрузки ->, ->*, .и .*может заполнить только в тех случаях , когда выражение будет определено, оно никогда не может изменить значение выражения , которое будет действительным, без перегрузки.

Potatoswatter
источник
2
Ваше последнее утверждение не совсем верно. Например, вы можете перегрузить newоператор, даже если он действителен, даже если он не перегружен.
Мэтт
6
@Matt хорошо, newвсегда перегружен, или правила перегрузки к нему на самом деле не применяются (13.5 / 5: функции выделения и освобождения, оператор new, operator new [], оператор delete и operator delete [], полностью описаны в 3.7 +0,4. атрибуты и ограничение , найденные в остальной части настоящего подпункта не распространяются на них , если явно не указаны в 3.7.4.) Но перегрузок унарных &или бинарный файл &&, ||или ,, или добавления перегрузок operator=или перегрузок просто о чем - нибудь для незаданного тип перечисления, может изменить значение выражения. Уточнил заявление, спасибо!
Potatoswatter
41

Оператор -> особенный.

"Он имеет дополнительные, нетипичные ограничения: он должен возвращать объект (или ссылку на объект), который также имеет оператор разыменования указателя, или он должен возвращать указатель, который можно использовать для выбора того, на что указывает стрелка оператора разыменования указателя. " Брюс Экель: Thinking CPP Vol-one: operator->

Дополнительный функционал предоставлен для удобства, поэтому вам не нужно звонить

a->->func();

Вы можете просто сделать:

a->func();

Это отличает оператор -> от других перегрузок оператора.

Totonga
источник
3
Этот ответ заслуживает большей похвалы, вы можете скачать книгу Эккеля по этой ссылке, и информация находится в главе 12 первого тома.
P i
26

Вы не можете перегрузить доступ к члену .(т.е. вторая часть того, что ->делает). Однако вы можете перегрузить унарный оператор разыменования* (т.е. первую часть того, что ->выполняет).

Оператор C ++ ->- это, по сути, объединение двух шагов, и это ясно, если вы думаете, что x->yон эквивалентен (*x).y. C ++ позволяет вам настроить, что делать с (*x)деталью, когда xона является экземпляром вашего класса.

Семантика ->перегрузки несколько странная, потому что C ++ позволяет либо вернуть обычный указатель (который будет использоваться для поиска указанного объекта), либо вернуть экземпляр другого класса, если этот класс также предоставляет ->оператор. Когда во втором случае поиск разыменованного объекта продолжается с этого нового экземпляра.

6502
источник
2
Отличное объяснение! Я предполагаю, что это означает то же самое для ->*, поскольку это эквивалентно форме (*x).*?
Bingo
10

->Оператор не знает , что элемент на который указывает на, он просто предоставляет объект для выполнения фактического доступа участника на.

Кроме того, я не вижу причин, по которым вы не можете предоставить версии с константой и неконстантой.

Джон Чедвик
источник
7

Когда вы перегружаете оператор -> () (здесь аргументы не передаются), компилятор фактически вызывает -> рекурсивно, пока он не вернет фактический указатель на тип. Затем он использует правильный член / метод.

Это полезно, например, для создания класса интеллектуального указателя, который инкапсулирует фактический указатель. Вызывается перегруженный оператор->, делает все, что он делает (например, блокирует для обеспечения безопасности потоков), возвращает внутренний указатель, а затем компилятор вызывает -> для этого внутреннего указателя.

Что касается константности - на него ответили в комментариях и других ответах (вы можете и должны предоставить оба).

Асаф
источник