ostream& operator<<(ostream& os, const WidthSetter& ws) {
ws(os); // Передать поток объекту ws
return(os); // для выполнения реальной работы
}
Это решает проблему, но не в общем виде. Вам не захочется писать класс типа WidthSetter для каждого вашего манипулятора, принимающего аргумент (возможно, вы это и делаете, но были бы не против поступить по-другому), поэтому лучше использовать шаблоны и указатели функций для получения привлекательной, обобщенной инфраструктуры, на базе которой вы можете создавать любое количество манипуляторов. Пример 10.5 содержит класс ManipInfra и версию operator<<, использующую аргументы шаблона для работы с различными типами символов, которые может содержать поток, и с различными типами аргументов, которые могут быть использованы манипулятором потока.
Пример 10.5. Инфраструктура манипуляторов
#include <iostream>
#include <string>
using namespace std;
// ManipInfra - это небольшой промежуточный класс, который помогает
// создавать специальные манипуляторы с аргументами. Вызывайте его
// конструктор с предоставлением указателя функции и значения из основной
// функции манипулятора
// Указатель функции должен ссылаться на вспомогательную функцию, которая
// делает реальную работу. См. примеры ниже
template<typename T, typename C>
class ManipInfra {
public:
ManipInfra(basic_ostream<C>& (*pFun) (basic_ostream<C>&, T), T val) :
manipFun_(pFun), val_(val) {}
void operator()(basic_ostream<C>& os) const {
manipFun_(os, val_);
} // Вызовите функцию, задавая ее указатель и
private: // передавая ей поток и значение
T val_;
basic_ostream<С>& (*manipFun_) (basic_ostream<C>&, T);
};
template<typename T, typename C>
basic_ostream<C>& operator<<(basic_ostream<C>& os,
const ManipInfra<T, C>& manip) {
manip(os);
return(os);
}
// Вспомогательная функция, которая вызывается в итоге в классе ManipInfra
ostream& setTheWidth(ostream& os, int n) {
os.width(n);
return(os);
}
// Собственно функция манипулятора. Именно она используется в клиентском
// программном коде
ManipInfra<int, char> setWidth(int n) {
return(ManipInfra<int, char>(setTheWidth, n));
}
// Ещё одна вспомогательная функция, которая принимает аргумент типа char
ostream& setTheFillChar(ostream& os, char с) {
os.fill(c);
return(os);
}
ManipInfra<char, char> setFill(char c) {
return(ManipInfra<char, char>(setTheFillChar, c));
}
int main() {
cout << setFill('-')
<< setWidth(10) << right << "Proust\n";
}
Если последовательность событий при работе этого класса все же остается неясной, я советую прогнать пример 10.5 через отладчик. Увидев его реальную работу, вы все поймете.
10.4. Создание класса, записываемого в поток
Требуется записать класс в поток для последующего его чтения человеком или с целью его хранения в постоянной памяти, т.е. для его сериализации.
Перегрузите operator<< для записи в поток соответствующих данных-членов. В примере 10.6 показано, как это можно сделать.
Пример 10.6. Запись объектов в поток
#include <iostream>
#include <string>
using namespace std;
class Employer {
friend ostream& operator<< // Он должен быть другом для
(ostream& out, const Employer& empr); // получения доступа к неоткрытым
public: // членам
Employer() {}
~Employer() {}
void setName(const string& name) {name_ = name;}
private:
string name_;
};
class Employee {
friend ostream& operator<< (ostream& out, const Employee& obj);
public:
Employee() : empr_(NULL) {}
~Employee() {if (empr_) delete empr_;}
void setFirstName(const string& name) {firstName_ = name;}
void setLasttName(const string& name) {lastName_ = name;}
void setEmployer(Employer& empr) {empr_ = &empr;}
const Employer* getEmployer() const {return(empr_);}
private:
string firstName_;
string lastName_;
Employer* empr_;
};
// Обеспечить передачу в поток объектов
Employer... ostream& operator<<(ostream& out, const Employer& empr) {
out << empr.name_ << endl; return(out);
}
// Обеспечить передачу в поток объектов Employee...
ostream& operator<<(ostream& out, const Employee& emp) {
out << emp.firstName_ << endl;
out << emp.lastName_ << endl;
if (emp.empr_) out << *emp.empr_ << endl;
return(out);
}
int main() {
Employee emp;
string first = "William";
string last = "Shatner";
Employer empr;
string name = "Enterprise";
empr.setName(name);
emp.setFirstName(first);
emp.setLastName(last);
emp.setEmployer(empr);
cout << emp; // Запись в поток
}
Прежде всего, необходимо объявить оператор operator<< другом (friend) класса, который вы хотите записывать в поток. Вы должны использовать operator<<, а не функцию-член типа writeToStream(ostream& os), потому что этот оператор принято использовать в стандартной библиотеке для записи любых объектов в поток. Вам придется объявить его другом, потому что в большинстве случаев потребуется записывать в поток закрытые члены, а не являющиеся друзьями функции не смогут получить доступ к ним.