Абстрактный класс и operator <<

Доброй ночи, товарищи! Возникла задача написать базовый, абстрактный класс. И перегрузить опретацию <<. Получается, что для достижения этой целин надо написать виртуальную, дружественную, нулевую функцию. Возможно ли такое или я что то нагородил?
14 ответов

делаешь виртуальный\чисто виртуальный оператор в базовом классе, например +=, потом переопределяешь его в потомке. И делаешь внешнюю ф-ию например +, и все..


Попробуй так
class ClassA{    virtual ostream& out(ostream& os) const = 0;    friend ostream& operator<<(ostream& os, const ClassA& a) {        return a.out(os);    }};class ClassB1 : public ClassA{    ostream& out(ostream& os) const {        os << "ClassB1::out(ostream& os)";        return os;    }};class ClassB2 : public ClassA{    ostream& out(ostream& os) const {        os << "ClassB2::out(ostream& os)";        return os;    }};


У меня почти такой же вариант, как и у Annihilator, только оператор << не дружественная функция.
class A{public:    virtual ~A() {}    virtual std::ostream & Print(std::ostream & strm) const = 0;};std::ostream & operator<<(std::ostream & os, const ClassA & a){       return a.Print();}


Я за версию Annihilator, хотя версия Torsten соответствует тому, что у Мейерса. Мотивация такая: out не предназначена для внешних пользователей — клиенты этой иерархии классов должны использовать operator<< — но есть одно исключение, сам operator<<: это значит, надо делать out закрытой, а operator<< френдить.Но вместо out лучше print, да (со строчной буквы!).


хотя версия Torsten соответствует тому, что у Мейерса
Насколько я помню Майерс за инкапсуляцию, а френды ее нарушают. Вообще френды надо юзать при крайней необходимости, я считаю.


 Майерс за инкапсуляцию
  Понимаете, все мы за инкапсуляцию: я, вы, Мейерс — но френдов время от времени используют даже “великие”. Просто Мейерс реализовал так и почему-то никак своего решения не объяснил (это, если вдруг интересно, More Effective, 25). Я пояснил, почему на мой взгляд открыть print в данном случае плохо: это, если непонятно, еще более очевидным образом нарушает инкапсуляцию. Есть еще такая мотивировка. Изначально operator<< хочется сделать членом класс (так и Мейерс, кстати, начинает рассказывать, но это очевидно и из общих соображений: функция вывода в поток должна иметь доступ к представлению, чтобы его непосредственно и выводить). Но если поступить так, то не получится реализовать привычную идиому os << foo; (придется задом наперед). Вот и идут на этот финт с print. Но если вы изначально хотели предоставить operator<< доступ к реализации, то чего уж бояться делать его френдом: ведь член это более близкая связь чем френд.С print есть еще проблема: пользователь класса будет смотреть и видеть две функции одного назначения: print и operator<<. Это ненужное раздувание интерфейса, умножение сущностей без необходимости (привет Оккаму). В случае с закрытой print для пользователя есть только одна функция выполняющая одну работу. Все предельно ясно.И все-таки меня терзают смутные сомнения, что я где-то читал вариант с закрытой print. Что за память...


С print есть еще проблема: пользователь класса будет смотреть и видеть две функции одного назначения: print и operator<<. Это ненужное раздувание интерфейса, умножение сущностей без необходимости (привет Оккаму). В случае с закрытой print для пользователя есть только одна функция выполняющая одну работу. Все предельно ясно.
Однако, тот, кто наследуется, должен знать о существовании print у базового класса. Т.е. print должна быть задокументирована.


Да, дельное замечание. Под клиентами я, разумеется, понимал классы, непосредственно использующие объекты рассматриваемого класса (с “виртуальной” функцией вывода в поток) и его наследников. Все-таки по умолчанию я предполагаю, что человек не наследуется от классов чужой библиотеки (наследование реализации многажды поруганная стратегия... впрочем, как и френдизм ). А в рамках одной библиотеки человек/команда проектирует иерархию классов со всеми виртуальностями и должны, конечно, быть осведомлены об используемом механизме.


Я пояснил, почему на мой взгляд открыть print в данном случае плохо: это, если непонятно, еще более очевидным образом нарушает инкапсуляцию. 
И чем же она нарушает инкапсуляцию ? Тем что выводит свои данные в поток ? Ее обычно и создают, чтобы выводить информацию, а не скрывать ее.
С print есть еще проблема: пользователь класса будет смотреть и видеть две функции одного назначения: print и operator<<. Это ненужное раздувание интерфейса, умножение сущностей без необходимости (привет Оккаму). В случае с закрытой print для пользователя есть только одна функция выполняющая одну работу. Все предельно ясно.
Можно создать класс printable, у которого будет переопределен оператор << и вызывается виртуальная функция print. Унаследованный класс должен будет переопредилить функцию print, и в тоже самое время ему не нужно будет определять оператор <<, т.к. он реализован для класса printable. Когда пользователь будет смотреть архитектуру класса он не будет видеть лишнего и сам интерфейс не будет раздуватся, т.к. достаточно будет в проивзодных классах определить только 1 функцию.
Есть еще такая мотивировка. Изначально operator<< хочется сделать членом класс (так и Мейерс, кстати, начинает рассказывать, но это очевидно и из общих соображений: функция вывода в поток должна иметь доступ к представлению, чтобы его непосредственно и выводить). Но если поступить так, то не получится реализовать привычную идиому os << foo; (придется задом наперед). Вот и идут на этот финт с print. Но если вы изначально хотели предоставить operator<< доступ к реализации, то чего уж бояться делать его френдом: ведь член это более близкая связь чем френд.
Это мнение ошибчно. Для того, чтобы это понять нужно понять "принцип интерфейса". В случае ostream & operator <<(ostream & strm, const Foo & foo) { /*код для вывода в поток */} в соотвествии с принципом интерфейса.Так как operator << упоминает Foo - он является логической часть Foo. operator << упоминает ostream, поэтому operator << зависит от ostream.operator << является логической частью Foo и зависит от ostream, следовательно Foo зависит от ostream. Таким образом реализация посредством френд оператора <<, ничем не лучше функции Print. А Print как я уже и писал выше имеет преимущество перед френд оператором в том, что в поток корректно выводтся все производные от X классы, даже при передаче оператору << ссылки производных классов.
Но вместо out лучше print, да (со строчной буквы!).
Это вопрос стиля и правил разработки принемаемых перед созданием проекта. 


И чем же она нарушает инкапсуляцию ? Тем что выводит свои данные в поток ? Ее обычно и создают, чтобы выводить информацию, а не скрывать ее.
Нет, не тем что выводит данные в поток, а тем, что создавалась она только для использования в operator<<, а а выложив ее в public, ей смогут пользоваться клиенты класса. Ее создают не для того, чтобы выводить данные в поток — видимо, вы позабыли, что print создавалась, чтобы задействовать механизм виртуальности, это деталь реализации operator<<, роль которого как раз вывод в поток — с точки зрения клиента.
Print как я уже и писал выше имеет преимущество перед френд оператором в том, что в поток корректно выводятся все производные от X классы, даже при передаче оператору << ссылки производных классов.
Вы не понимаете: это не преимущество print, а ее назначение: обеспечивать виртуальность. Если бы она этого не делала, обошлись бы без нее. Ну, или без operator<<. Если уж решили его сделать, то он должен быть единственным средством вывода в поток.“Принцип интерфейса” не осилил  Еще один аргумент закрытия print это совет из Стандартов кодирования Саттера&Александреску, повторяемый у Саттера в More Exceprtional и Дьюхерста в Готчах (или если по хронологии, то наоборот, кажется): делайте виртуальные функции закрытыми (идиома non-virtual interface, NVI). В данном случае невиртуальный интерфейс это operator<<, а спрятанная по канонам Саттера-Александреску-Дьюхерста виртуальность это print.


Нет, не тем что выводит данные в поток, а тем, что создавалась она только для использования в operator<<, а а выложив ее в public, ей смогут пользоваться клиенты класса.
Ну и что дальше ? Ну могут они данные вывести, но данные изменять не могут и кроме того они понятие не имеют о том что выводится, так что здесь все нормально.
Вы не понимаете: это не преимущество print, а ее назначение: обеспечивать виртуальность. Если бы она этого не делала, обошлись бы без нее. Ну, или без operator<<. Если уж решили его сделать, то он должен быть единственным средством вывода в поток.
Ну если вы не понимаете, тогда я обьясню на коде.
class Message{public:      vritual ~Message() {}...};ostream operator << (ostream & strm, const Message & Msg) { ... }class SystemMessage : public Message {...};ostream operator << (ostream & strm, const SystemMessage & Msg) { ... }class UserMessage : public Message {...};ostream operator << (ostream & strm, const UserMessage & Msg) { ... }Message * pSystem=new SystemMessage;Message * pUser=new UserMessage;cout << pSystem << pUser ;
Будет использоватся оператор вывода для абстрактного класса Message.Если реализовывать с помощью функции Print, то во-первых не нужно будет писать для каждого класса оператор <<, а во-вторых самое главное, всегда будут выводится реальные данные, то есть SystemMessage и UserMessage, т.к. функция Print будет вызвана у них.


Ну и что дальше ? Ну могут они данные вывести, но данные изменять не могут и кроме того они понятие не имеют о том что выводится, так что здесь все нормально.
Так вы полагаете что инкапсуляция означает невозможность изменить данные? Ну, спешу вас огорчить, это намного более общее понятие: инкапсуляция подразумевает сокрытие реализации класса и его артефактов (в C++ — в частности, тесно связанных с классом свободных функций). Я надеюсь, вы когда-нибудь использовали private-методы. Это тоже инкапсуляция. Надеюсь, когда-нибудь использовали private-константы внутри класса: их можно было бы открыть, потому что их изменить нельзя (хаки с const-кастом не берем, можно рассмотреть другие ОО-языки, в которых подобных cast-ов нет). Однако, закрытые константы используются. Это тоже инкапсуляция.И скрытие деталей того, как виртуализирована operator<<, то есть закрытие print, это тоже инкапсуляция.
Ну если вы не понимаете, тогда я объясню на коде.
О! Вы попытались объяснить мне идиому виртуальной свободной функции, как она описана у Мейерса (см. мою ссылку выше)!   Чтоб я б я без вас делал...


Извините, что долго отсутствовал. Спасибо всем кто ответил!


наследование реализации многажды поруганная стратегия... 
Те, кто её ругают, идут лесом. Делая Print закрытой, мы лишаем пользователя возможности осуществлять невиртуальный вызов для базового подобъекта.