Почему оператор стрелки в C ++ не является псевдонимом *.?

18

В c ++ оператор * может быть перегружен, например, с помощью итератора, но оператор стрелки (->) (. *) Не работает с классами, которые перегружают оператор *. Я полагаю, что препроцессор может легко заменить все экземпляры -> на (* left) .right, и это сделает итераторы более привлекательными для реализации. Есть ли практическая причина отличаться -> или это просто особенность языка / дизайнеров?

Якоб Вайсблат
источник

Ответы:

16

Правило, foo->barравное (*foo).barтолько для встроенных операторов.

Унарный operator *не всегда имеет семантику разыменования указателя. Я мог бы создать библиотеку, в которой это означало бы перестановку матриц, совпадения парсера с нулем или более или почти все что угодно.

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

operator -> может быть перегружен отдельно, поэтому, если вы хотите его, вы можете перегрузить его с минимальными усилиями.

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

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <iostream>
#include <ostream>

struct Foo
{
    boost::shared_ptr<std::string> operator -> () const
    {
        return boost::make_shared<std::string>("trololo");
    }
};

int main()
{
    Foo foo;
    std::cerr << foo->size() << std::endl;
}
Ларс Виклунд
источник
Что иллюстрирует ваш пример? Вы возвращаете умный указатель на строку и как-то выводите размер? Я не совсем понимаю.
Тревор Хикки
2
Он иллюстрирует последний абзац моего ответа: как использовать ->цепочки операторов, пока не получит необработанный указатель на что-то, разыменовывать и получать доступ к члену этого оператора. Если оператор -> не цепочка, пример будет некорректным, так как shared_ptr не является необработанным указателем.
Ларс Виклунд,
@LarsViklund: у вашего ответа есть проблема: вы сказали «оператор-> ... автоматически связывает оператор-> вызовы до тех пор, пока один из них не вернет необработанный указатель». Это не правильно - использование A->Bцепочек синтаксиса не более 1 дополнительного вызова. То, что на самом деле делает двоичный синтаксис C ++ ->, не вызывает объект opeartor->напрямую - вместо этого он смотрит на тип Aи проверяет, является ли он необработанным указателем. Если он затем ->разыменовывает его и выполняет Bдля него, в противном случае он вызывает объект operator->, разыменовывает результат (либо используя собственный необработанный указатель, либо другой, operator->а затем выполняет Bрезультат
Guss
@Guss: Я не могу найти ни главу и стих для вашего требования, ни воспроизвести его в компиляторе. C ++ 11 13.5.6 / 1 указывает, что, если существует подходящая перегрузка, x->mследует интерпретировать как (x.operator->())->m. Если LHS operator->снова имеет подходящую перегрузку , этот процесс повторяется до тех пор, пока не будет получен обычный (*x).mэффект 5.2.5 / 2.
Ларс Виклунд
8

«Язык программирования C ++» описывает тот факт, что эти операторы могут быть разными, но также говорит:

Если вы предоставляете более одного из этих операторов, возможно, было бы целесообразно обеспечить эквивалентность, так же, как это разумно, чтобы гарантировать ++xи x+=1иметь тот же эффект, что и x=x+1для простой переменной xнекоторого класса if ++, + =, =, и + предоставляются.

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


источник
7

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

Тем не менее, итераторы сделать поддержку либо использование. В древних реализациях вы можете найти библиотеку, которая требует (*iter).whateverвместо iter->whatever, но если это так, то это ошибка в реализации, а не характеристика языка. Учитывая объем работы, связанной с реализацией всех стандартных контейнеров / алгоритмов / итераторов, неудивительно, что некоторые ранние выпуски были несколько неполными, но на самом деле они никогда не предназначались для этого.

Джерри Гроб
источник
Я не понял, реализованы ли стандартные библиотечные контейнеры -> или что он был перегружен.
Якоб Вайсблат
3
C ++ 03 24.1 / 1 требует, чтобы любой итератор, который (*i).mявляется допустимым, поддерживал i->mту же семантику.
Ларс Виклунд