Подтвердить что ты не робот

Как работают манипуляторы потока?

Хорошо известно, что пользователь может определять манипуляторы потоков следующим образом:

ostream& tab(ostream & output)
{
    return output<< '\t';
} 

И это можно использовать в main() следующим образом:

cout<<'a'<<tab<<'b'<<'c'<<endl;

Пожалуйста, объясните мне, как это все работает? Если оператор < принимает в качестве второго параметра указатель на функцию, которая принимает и возвращает ostream &, то, пожалуйста, объясните, почему это необходимо? Что было бы неправильно, если функция не принимает и возвращает ostream &, но она была недействительной вместо ostream &?

Также интересно, почему манипуляторы "dec", "hex" вступают в силу до тех пор, пока я не изменюсь между ними, но пользовательские манипуляторы должны всегда использоваться для вступления в силу для каждой потоковой передачи?

4b9b3361

Ответ 1

Стандарт определяет следующую перегрузку operator<< в шаблоне класса basic_ostream:

basic_ostream<charT,traits>& operator<<(
    basic_ostream<charT,traits>& (*pf) (basic_ostream<charT,traits>&) );

Эффекты: Нет. Не ведет себя как форматированная выходная функция (как описано в 27.6.2.5.1).

Возвращает: pf(*this).

Параметр - это указатель на функцию, принимающую и возвращающую ссылку на std::ostream.

Это означает, что вы можете "передать" функцию с этой сигнатурой в объект ostream, и она имеет эффект вызова этой функции в потоке. Если вы используете имя функции в выражении, то оно (обычно) преобразуется в указатель на эту функцию.

std::hex является манипулятором std::ios_base, определенным следующим образом.

   ios_base& hex(ios_base& str);

Эффекты: Вызовы str.setf(ios_base::hex, ios_base::basefield).

Возвращает: str.

Это означает, что потоковая передача hex на ostream будет устанавливать флаги форматирования выходной базы для вывода чисел в шестнадцатеричном формате. Манипулятор ничего не выводит.

Ответ 2

Нет ничего плохого в этом, за исключением того, что нет перегруженного < < оператор, определенный для него. Существующие перегрузки для < ожидают манипулятора с сигнатурой ostream & (* FP) (ostream &).

Если вы дали ему манипулятор с типом ostream & (* fp)() вы получили бы ошибку компилятора, так как не имеет определение для оператора < < (ostream &, ostream & (* fp)() ). Если вы хотите эту функциональность, вам придется перегрузить < оператора для приема манипуляторов этого типа.

Вам нужно написать определение для этого:
ostream & ostream:: operator < (ostream & (* m)())

Имейте в виду, что здесь ничего магического не происходит. Библиотеки потоков в значительной степени зависят от стандартных возможностей С++: перегрузка операторов, классы и ссылки.

Теперь, когда вы знаете, как вы можете создать описанную вами функциональность, вот почему мы этого не делаем:

Не передавая ссылку на поток, который мы пытаемся манипулировать, мы не можем вносить изменения в поток, подключенный к окончательному устройству (cin, out, err, fstream и т.д.). Функция (модификатор - все просто функции с причудливыми именами) либо должна была бы вернуть новый поток, который не имел ничего общего с тем, что слева от символа < оператора или через какой-то очень уродливый механизм, выясните, какой ostream он должен подключиться, иначе все, что направо от модификатора, не будет доведено до конечного устройства, но скорее будет отправлено в любой поток, возвращаемый функцией/модификатором.

Подумайте о таких потоках

cout << "something here" << tab << "something else"<< endl;

действительно означает

(((cout << "something here") << tab ) << "something else" ) << endl);

где каждый набор круглых скобок делает что-то cout (запись, изменение и т.д.), а затем возвращает cout, поэтому следующий набор круглых скобок может работать на нем.

Если ваш модификатор/функция вкладки не принимал ссылку на ostream, ему нужно было бы как-то угадать, какой ostream был слева от < оператора для выполнения своей задачи. Вы работали с курсом, cerr, некоторым файловым потоком...? Внутренности функции никогда не узнают, если они не передадут эту информацию каким-то образом, и почему бы не так, как это было бы просто, как ссылка на нее.

Теперь, чтобы действительно поместить точку дома, давайте посмотрим, что действительно есть endl и какая перегруженная версия < мы используем:

Этот оператор выглядит следующим образом:

  ostream& ostream::operator<<(ostream& (*m)(ostream&)) 
  {  
      return (*m)(*this);
  }

endl выглядит следующим образом:

  ostream& endl(ostream& os)      
  {  
      os << '\n'; 
      os.flush();     
      return os;
  }

Цель endl - добавить новую строку и очистить поток, убедившись, что все содержимое внутреннего буфера потока записано на устройство. Для этого сначала нужно написать '\n' этому потоку. Затем ему нужно сообщить потоку, чтобы он промолчал. Единственный способ, с помощью которого endl узнать, какой поток писать и скрывать, - это передать оператор этой функции функции endl при ее вызове. Было бы так, как будто я говорю, что ты моешь свою машину, но никогда не говоришь, какая машина моя на всей стоянке. Вы никогда не сможете выполнить свою работу. Мне нужно, чтобы я либо передал тебе свою машину, либо сам помылся.

Я надеюсь, что это очистит вещи

PS - Если вы случайно случайно нашли мой автомобиль, пожалуйста, вымойте его.

Ответ 3

Обычно манипулятор потока устанавливает некоторые флаги (или другие настройки) для объекта потока, так что в следующий раз, когда он будет использоваться, он будет действовать в соответствии с флагами. Поэтому манипулятор возвращает тот же объект, который был передан. Перегрузка operator<<, которая называется манипулятором, уже имеет этот объект, конечно, так, как вы заметили, возвращаемое значение строго не требуется для этого случая. Я думаю, что это охватывает все стандартные манипуляторы - все они возвращают свой вклад.

Однако с возвращаемым значением структура достаточно гибкая, чтобы пользовательский манипулятор потока мог возвращать другой объект, предположительно, оболочку для объекта, который он задал. Этот другой объект затем будет возвращен из cout << 'a' << tab и может сделать то, что не поддерживает встроенные параметры форматирования ostream.

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