Могут ли устройства вставки и извлечения iostream быть членами класса вместо глобальных перегрузок? - PullRequest
2 голосов
/ 22 октября 2011

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

(Примечание: если у кого-то есть более хороший Google-Fu, чтобы найти хороший ответ, уже написанный, мне было бы интересно прочитать это.)

Что я подозреваю, так это то, что это технически возможно, и это только нотационная проблема.Если бы библиотека была разработана для перегрузок элементов << и >>, вам пришлось бы строить строку потоковых операций справа налево, а не слева направо.Поэтому вместо того, чтобы писать:

Rational r (1, 2);
cout << "Your rational number is " << r;

Вы должны записать строку вывода в виде:

r >> ("Your rational number is " >> cout);

Скобки необходимы, чтобы начать обратное связывание, потому что >> и << ассоциируется слева направо.Без них он попытается найти совпадение для r >> "Your rational number is " до "Your rational number is " >> cout.Если бы был выбран оператор с ассоциативностью справа налево , этого можно было бы избежать:

r >>= "Your rational number is " >>= cout;

(Примечание. Внутри библиотеки не относящиеся к классам типы, такие как строкас литералом нужно было бы позаботиться о глобальных перегрузках операторов.)

Но является ли это пределом, и это обращение в значительной степени неизбежно для любого дизайна в стиле iostream, который хотел, чтобы сериализация отправлялась в класс?Я пропускаю какие-либо другие проблемы?


ОБНОВЛЕНИЕ Возможно, лучше сформулировать "проблему", чтобы сказать, что я подозревал следующее:

Для непотоковых объектов, которые хотят сериализовать себя, библиотека iostream МОЖЕТ гипотетически спроектирована так, чтобы вставки и экстракторы были членами класса вместо глобальных перегрузок ... и без (значительного) влияния на свойства среды выполнения.Тем не менее, это работало бы ТОЛЬКО, если бы авторы iostream согласились с этим, что заставило бы клиентов формировать потоковые операции справа налево.

Но мне не хватает интуиции о том, почему глобальная перегрузка оператора противучастник может разблокировать иначе разблокируемую способность выражать себя слева направо (вместо справа налево).Вопрос в том, могут ли альтернативные варианты задним числом или шаблоны, или какая-то эзотерическая особенность C ++ 11.ИЛИ имеет ли «физика С ++» присущий одно направление другому, и глобальная перегрузка является каким-то единственным приемом компиляции во время компиляции в книге для ее переопределения.

Сравните с Правило левой руки Флемингадвигатели

Ответы [ 5 ]

3 голосов
/ 22 октября 2011

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

Обоснование:

Почему inserters и extractors не перегружены как функции-члены?

Обычно правило перегрузки оператора:

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

По этому правилу потоковые операторы должны быть реализованы как члены типа их левого операнда. Однако их левые операнды являются потоками из стандартной библиотеки, и One не может изменить типы потоков стандартной библиотеки. Следовательно, при перегрузке этих операторов для пользовательских типов они обычно реализуются как функции, не являющиеся членами.

2 голосов
/ 16 июня 2013

У меня раньше был тот же вопрос, и похоже, что вставки и экстракторы должны быть глобальными. Для меня это нормально; Я просто хочу, чтобы в моем классе не было больше функций «друга». Вот как я это делаю, то есть, предоставляя общедоступный интерфейс, который "<<" может вызывать: </p>

class Rock {
    private:
        int weight;
        int height;
        int width;
        int length;

    public:
        ostream& output(ostream &os) const {
            os << "w" <<  weight << "hi" << height << "w" <<  width << "leng" << length << endl;
            return os;
        }
    };

    ostream& operator<<(ostream &os, const Rock& rock)  {
        return rock.output(os);
    }
2 голосов
/ 24 октября 2011

Вы получаете больше гибкости, отделяя определение << и >> от отображаемого объекта.

Прежде всего, вы можете захотеть отображать пользовательский тип, не относящийся к классу, такой как enum или указатель. Предположим, у вас есть класс Foo, и вы хотите напечатать указатель на Foo. Вы не можете сделать это, добавив функцию-член в Foo. Или вы можете отобразить шаблонный объект, но только для определенного параметра шаблона. Например, вы можете отобразить вектор в виде списка через запятую, а вектор в виде столбца строки.

Другая причина может заключаться в том, что вы не можете или не хотите изменять существующий класс. Это хороший пример принципа открытия / закрытия в ОО-дизайне, где вы хотите, чтобы ваши классы были открыты для расширения, но закрыты для модификации. Конечно, ваш класс должен предоставлять некоторые из своих реализаций без нарушения инкапсуляции, но это часто бывает (векторы представляют свои элементы, сложные раскрывают Re и Im, строковые представляют c_str и т. Д.).

Вы можете даже определить две разные перегрузки << для одного и того же класса в разных модулях, если это имеет смысл. </p>

1 голос
/ 24 октября 2011

В C ++ бинарные операторы обычно не являются членами класса.Согласно C ++ Programming Language от оператора Бьярна Страуструпа + каноническое представление - это глобальная функция, которая сначала копирует свой левый операнд, а затем использует + = с правым операндом, а затем возвращает результат.Таким образом, иметь глобальные операторы потока вовсе не необычно.Как упомянул Алс, мы ожидаем, что операторы потока будут членами потоковых классов, а не классов данных.

1 голос
/ 23 октября 2011

Я пропускаю какие-либо другие проблемы?

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

Обычный outfile << var1 << var2 << var3; - довольно "линейный" синтаксис. И, поскольку мы читаем слева направо в обоих случаях, имена будут в том же порядке, что и в файле.

Вы планируете сделать синтаксис нелинейным. Человек-читатель должен пропустить и вернуться, чтобы увидеть, что происходит. Это делает это сложнее. И вы идете еще дальше. Чтобы прочитать вашу последнюю строку, r >>= "Your rational number is " >>= cout;, сначала вы должны прочитать вперед через >> =, чтобы увидеть, что вам нужно перейти к последнему слову (или около того), прочитать «>> = cout», перейти к началу строка, читать вперед через строку и так далее. В отличие от перемещения ваших глаз от одного маркера к другому, где мозг способен направлять весь процесс.

У меня есть несколько лет опыта работы с языком с таким нелинейным синтаксисом. Сейчас я изучаю использование clang для «компиляции» C ++ в этот язык. (Однако, по нескольким причинам.) Если это сработает, я буду намного счастливее.

Моя предпочтительная альтернатива - очень минимальная перегрузка оператора, которая вызывает только функцию-член. Если оптимизировать хоть немного, он все равно исчезнет из созданного исполняемого файла.

Существует одно «очевидное» исключение из вышеприведенного: когда в выражении есть только одно чтение / запись, как в myobj >> cout;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...