| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > Абстрактный класс и operator << |
| Автор: T0ohtik 19.7.2008, 01:12 |
| Доброй ночи, товарищи! Возникла задача написать базовый, абстрактный класс. И перегрузить опретацию <<. Получается, что для достижения этой целин надо написать виртуальную, дружественную, нулевую функцию. Возможно ли такое или я что то нагородил? |
| Автор: vinter 19.7.2008, 08:57 |
| делаешь виртуальный\чисто виртуальный оператор в базовом классе, например +=, потом переопределяешь его в потомке. И делаешь внешнюю ф-ию например +, и все.. |
| Автор: Annihilator 19.7.2008, 09:49 | ||
Попробуй так
|
| Автор: Torsten 19.7.2008, 11:19 | ||
У меня почти такой же вариант, как и у Annihilator, только оператор << не дружественная функция.
|
| Автор: Ulysses4j 19.7.2008, 19:00 |
| Я за версию Annihilator, хотя версия Torsten соответствует тому, что у Мейерса. Мотивация такая: out не предназначена для внешних пользователей — клиенты этой иерархии классов должны использовать operator<< — но есть одно исключение, сам operator<<: это значит, надо делать out закрытой, а operator<< френдить. Но вместо out лучше print, да (со строчной буквы!). |
| Автор: vinter 19.7.2008, 21:34 |
Насколько я помню Майерс за инкапсуляцию, а френды ее нарушают. Вообще френды надо юзать при крайней необходимости, я считаю. |
| Автор: Ulysses4j 19.7.2008, 22:16 |
Есть еще такая мотивировка. Изначально operator<< хочется сделать членом класс (так и Мейерс, кстати, начинает рассказывать, но это очевидно и из общих соображений: функция вывода в поток должна иметь доступ к представлению, чтобы его непосредственно и выводить). Но если поступить так, то не получится реализовать привычную идиому os << foo; (придется задом наперед). Вот и идут на этот финт с print. Но если вы изначально хотели предоставить operator<< доступ к реализации, то чего уж бояться делать его френдом: ведь член это более близкая связь чем френд. С print есть еще проблема: пользователь класса будет смотреть и видеть две функции одного назначения: print и operator<<. Это ненужное раздувание интерфейса, умножение сущностей без необходимости (привет Оккаму). В случае с закрытой print для пользователя есть только одна функция выполняющая одну работу. Все предельно ясно. И все-таки меня терзают смутные сомнения, что я где-то читал вариант с закрытой print. Что за память... |
| Автор: Ulysses4j 19.7.2008, 23:57 |
| Да, дельное замечание. Под клиентами я, разумеется, понимал классы, непосредственно использующие объекты рассматриваемого класса (с “виртуальной” функцией вывода в поток) и его наследников. Все-таки по умолчанию я предполагаю, что человек не наследуется от классов чужой библиотеки (наследование реализации многажды поруганная стратегия... впрочем, как и френдизм |
| Автор: Torsten 20.7.2008, 00:45 | ||||||||
И чем же она нарушает инкапсуляцию ? Тем что выводит свои данные в поток ? Ее обычно и создают, чтобы выводить информацию, а не скрывать ее.
Можно создать класс printable, у которого будет переопределен оператор << и вызывается виртуальная функция print. Унаследованный класс должен будет переопредилить функцию print, и в тоже самое время ему не нужно будет определять оператор <<, т.к. он реализован для класса printable. Когда пользователь будет смотреть архитектуру класса он не будет видеть лишнего и сам интерфейс не будет раздуватся, т.к. достаточно будет в проивзодных классах определить только 1 функцию.
Это мнение ошибчно. Для того, чтобы это понять нужно понять "принцип интерфейса". В случае ostream & operator <<(ostream & strm, const Foo & foo) { /*код для вывода в поток */} в соотвествии с принципом интерфейса. Так как operator << упоминает Foo - он является логической часть Foo. operator << упоминает ostream, поэтому operator << зависит от ostream. operator << является логической частью Foo и зависит от ostream, следовательно Foo зависит от ostream. Таким образом реализация посредством френд оператора <<, ничем не лучше функции Print. А Print как я уже и писал выше имеет преимущество перед френд оператором в том, что в поток корректно выводтся все производные от X классы, даже при передаче оператору << ссылки производных классов.
Это вопрос стиля и правил разработки принемаемых перед созданием проекта. |
| Автор: Ulysses4j 20.7.2008, 01:20 | ||||
Нет, не тем что выводит данные в поток, а тем, что создавалась она только для использования в operator<<, а а выложив ее в public, ей смогут пользоваться клиенты класса. Ее создают не для того, чтобы выводить данные в поток — видимо, вы позабыли, что print создавалась, чтобы задействовать механизм виртуальности, это деталь реализации operator<<, роль которого как раз вывод в поток — с точки зрения клиента.
Вы не понимаете: это не преимущество print, а ее назначение: обеспечивать виртуальность. Если бы она этого не делала, обошлись бы без нее. Ну, или без operator<<. Если уж решили его сделать, то он должен быть единственным средством вывода в поток. “Принцип интерфейса” не осилил Еще один аргумент закрытия print это совет из Стандартов кодирования Саттера&Александреску, повторяемый у Саттера в More Exceprtional и Дьюхерста в Готчах (или если по хронологии, то наоборот, кажется): делайте виртуальные функции закрытыми (идиома non-virtual interface, NVI). В данном случае невиртуальный интерфейс это operator<<, а спрятанная по канонам Саттера-Александреску-Дьюхерста виртуальность это print. |
| Автор: Torsten 20.7.2008, 21:31 | ||||||
Ну и что дальше ? Ну могут они данные вывести, но данные изменять не могут и кроме того они понятие не имеют о том что выводится, так что здесь все нормально.
Ну если вы не понимаете, тогда я обьясню на коде.
Будет использоватся оператор вывода для абстрактного класса Message. Если реализовывать с помощью функции Print, то во-первых не нужно будет писать для каждого класса оператор <<, а во-вторых самое главное, всегда будут выводится реальные данные, то есть SystemMessage и UserMessage, т.к. функция Print будет вызвана у них. |
| Автор: Ulysses4j 20.7.2008, 21:47 | ||
Так вы полагаете что инкапсуляция означает невозможность изменить данные? Ну, спешу вас огорчить, это намного более общее понятие: инкапсуляция подразумевает сокрытие реализации класса и его артефактов (в C++ — в частности, тесно связанных с классом свободных функций). Я надеюсь, вы когда-нибудь использовали private-методы. Это тоже инкапсуляция. Надеюсь, когда-нибудь использовали private-константы внутри класса: их можно было бы открыть, потому что их изменить нельзя (хаки с const-кастом не берем, можно рассмотреть другие ОО-языки, в которых подобных cast-ов нет). Однако, закрытые константы используются. Это тоже инкапсуляция. И скрытие деталей того, как виртуализирована operator<<, то есть закрытие print, это тоже инкапсуляция. О! Вы попытались объяснить мне идиому виртуальной свободной функции, как она описана у Мейерса (см. мою ссылку выше)! |
| Автор: T0ohtik 20.7.2008, 22:37 |
| Извините, что долго отсутствовал. Спасибо всем кто ответил! |
| Автор: UnrealMan 21.7.2008, 12:35 |
Те, кто её ругают, идут лесом. Делая Print закрытой, мы лишаем пользователя возможности осуществлять невиртуальный вызов для базового подобъекта. |
| Автор: Ulysses4j 21.7.2008, 13:18 |
| Ну, если считаете необходимым вызывать в потомках, делайте protected. Я же говорю: видимость должна отражать назначение. Ну-ну, бедные-бедные ребята, эти авторы красных книжек с автором серии во главе: ушли лесом... |
| Автор: UnrealMan 21.7.2008, 14:01 | ||
Прежде чем читать умные книжки, надо выучить букварь. А то получается "гляжу в книгу, а вижу фигу". У некоторых людей просто мания какая-то путать частное с общим. |
| Автор: Ulysses4j 21.7.2008, 14:12 |
| А, то есть вы не осили букварь и теперь кругом видите фиги? |
| Автор: UnrealMan 21.7.2008, 14:32 | ||
Неумение внимательно читать книги, равно как и отсутствие самокритики, - это твоя проблема, а мне плевать на чужие проблемы :p |
| Автор: Ulysses4j 21.7.2008, 14:41 |
| Ой, а можно пример моего невнимательного чтения книг? (Самокритику опустим.) |
| Автор: UnrealMan 21.7.2008, 15:44 | ||
Контрпример можно найти прямо под носом: std::basic_ostream из стандартной библиотеки C++. Если пользователь захочет реализовать вывод информации на некоторое устройство через потоки, то он может создать новый потоковый класс, унаследовав его от std::basic_ostream (подобно тому, как это делается для basic_ofstream и basic_ostringstream). Данный базовый класс - вовсе никакой не абстрактный, и наследование реализации здесь имеет место быть. Никаких противоречий с рассуждениями, приводимыми в прочитанных мной книгах классиков, здесь нет. То, что является потоком вывода, должно быть унаследовано от std::basic_ostream. |
| Автор: Ulysses4j 21.7.2008, 16:14 |
| Я же не говорил, что нужно запретить наследование реализации. В некоторых случаях избежать ее нельзя или трудно, это верно. Пример хороший, он демонстрирует слабость стандартной библиотеки, которая вынуждает вас наследовать реализацию, когда вы хотите наследовать интерфейс. Стандартная библиотека C++ не идеальна, как и не идеален C++, в котором понятие интерфейса не имеет воплощения в синтаксисе языка. Учтите. кстати, что все-таки есть определенная разница между стандартной библиотекой языка и сторонней библиотекой, к которой я аппелировал в посте. Классы потоков зашиты во многих интерфейсах именно потому что они входят в состав стандартных средств, в этой ситуации наследование от них для того, чтобы быть повторно использованным, а не для того, чтобы повторно использовать (© не помню кто), может быть вполне оправдано. Еще раз прочитайте то, что вы отцитировали: я же говорю «по умолчанию». Если вы можете обосновать необходимость использования чего-то в конкретном случае, это замечательно, пользуйтесь. В том посте, да и в следующем вашем комментарии про использовании print в наследниках, я вас поддержал. Собственно, я не понимаю, в чем педмет ваших претензий ко мне. |
| Автор: UnrealMan 21.7.2008, 16:50 | ||||
"Некоторых" - это вроде как немногочисленных? Откуда такая статистика?
О да, мы просто мечтаем унаследовать голый интерфейс, а всё форматирование, контроль за состоянием и т.д. реализовать с нуля ручками
Не осилил разницу. Стандартная библиотека, или нестандартная, она может предоставлять некий базовый функционал, который пользователю может быть позволено расширять при помощи наследования, а не только использовать как часть чего-то. |
| Автор: Ulysses4j 21.7.2008, 17:01 | ||||
От друзей, зарабатывающих на жизнь программированием, и из книжек.
Нет, не мечтаем. Для этого мы мечтаем использовать публичный интерфейс классов. Я так понял, что вы хотите наследоваться от basic_ostream в вашем примере, чтобы ваш класс мог использоваться там, где уже используется ostream, то есть — много где(здесь возникает аргумент о том, что стандартная библиотека отличается от обыкновенной — своей распространенностью; надеюсь, больше повторять не придется). Если вы хотели использовать какие-то protected-члены ostream, приведите, пожалуйста, пример, какие. Большая часть классов стандартной библиотеки не предназначена для наследования реализации, там никаких особо продуманных protected-интерфейсов нет, так что я с интересом бы послушал, что вам там приглянулось... |
| Автор: NDQuattro 21.7.2008, 17:10 | ||||
Люди, скажите плз может ли такому определению функции
|
| Автор: JackYF 21.7.2008, 17:19 |
| NDQuattro, создавай отдельную тему! |
| Автор: NDQuattro 21.7.2008, 17:20 |
| ок, просто ради такой мелочи форум мусорить не хотелось что то)) |
| Автор: Annihilator 21.7.2008, 17:44 | ||
Какие проблемы? Читай книги и не надо будет мусорить! (сорри |
| Автор: UnrealMan 21.7.2008, 17:52 | ||||
Этих друзей, видимо, сотни, каждый из которых программирует в своей уникальной предметной области и притом лично сообщил тебе статистические данные своей работы? Что-то с трудом верится. А иначе это получается не очень репрезентативно. Это уже обнадёживает, правда не хватает немного конкретики (автор и название хотя бы одной из этих книг, глава, пункт, абзац).
По-моему, совершенно очевидно, зачем нужно это наследование. Во-первых, basic_ostream берёт на себя много рутинной работы, во-вторых, классу, переопределяющему операцию вывода <<, нет необходимости различать, в какой именно поток будут выводить информацию, будь это cout, объект типа ofstream, объект типа ostringstream или объект типа MyOStream - с каждым видом потока можно работать единообразно.
Я не понимаю, каким боком распространённость в данном плане на что-либо влияет. |
| Автор: Ulysses4j 21.7.2008, 20:04 |
| Так, похоже, из того, что я писал выше, вы осили процентов 20. Я считаю такой кпд слишком низким для нашей увлекательной беседы, так что засим разрешите откланиться. |