Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.147.242.22] |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Всем привет!
В последнее время немного интересовался более другими языками, кроме как С++ и у каждого свои фишки по сабжу. Ну если более предметно, очень понравился Rust и Dart. Второй наверное даже более интересен. Ну это так, типа "взгляда ребёнка", которому показали коробочку с хэлоу ворлдами. Но! Естественно, первое знакомство с этими ЯП волей-не-волей сформировало некий фидбэк к С++ и Perl, которые я очень уважаю. А еще я познакомился и выучил наизусть догмы из S.O.L.I.D, еще одно дополнительное измерение. В общем - в голове какая-то каша от всего этого. Ничего не понимаю - но очень интересно! Хочется все эти знания-понятия как-то логически разложить по логическим полочкам в разрезе С++ возможностей. Вопросов куча, и скорее не "как?", а "для каких случаев лучше?". Понятное дело, что приветствие в начале поста было всем ... а просьба показать и рассказать "на пальцах" сами-знаете-кому Уверен, если оценить сабж с точки зрения описания темы - будет суперский материал для FAQ C++, ну и нам, недотепам - материал для утренних намазов. P.S. Всех страждущих прошу не скромничать! Тема будет полезна всем. |
Сообщ.
#2
,
|
|
|
Цитата Majestio @ Он с нескрываемым интересом послушает. а просьба показать и рассказать "на пальцах" сами-знаете-кому |
Сообщ.
#3
,
|
|
|
Вредина! Давай делись тайными знаниями!
|
Сообщ.
#4
,
|
|
|
В смысле? Чё ты от меня хошь? Что мульён раз уже говорено? Я думал, счас кто-то что-то скажет, и у меня вдруг случится сдвиг мозга.
|
Сообщ.
#5
,
|
|
|
Цитата Qraizer @ В смысле? Чё ты от меня хошь? Что мульён раз уже говорено? Я думал, счас кто-то что-то скажет, и у меня вдруг случится сдвиг мозга. Что-то я запамятовал, где такое обсуждалось. Ну а сказать, что кто-то скажет ... сложно, осталось совсем мало собеседников. |
Сообщ.
#6
,
|
|
|
Оставлю тут, из небольшой беседы с ChatGPT
Наследование Наследование в объектно-ориентированном программировании позволяет классу наследовать свойства и методы другого класса. Это позволяет создавать иерархии классов, где дочерние классы могут наследовать функциональность от родительских классов. Наследование способствует повторному использованию кода и уменьшению дублирования. Композиция Композиция представляет собой отношение между классами, где один класс включает в себя объект другого класса в качестве одного из своих полей. Это позволяет создавать более сложные объекты, используя уже существующие классы. Композиция способствует созданию более гибкой структуры программы и уменьшению связанности между классами. Агрегация Агрегация также представляет отношение между классами, где один класс содержит ссылку на другой класс в качестве одного из своих полей. Основное отличие от композиции заключается в том, что объекты, связанные агрегацией, могут существовать независимо друг от друга. Агрегация позволяет создавать более сложные объекты, оставляя возможность изменения и замены частей объекта. Интерфейсы Интерфейсы представляют собой контракты, определяющие методы, которые должны быть реализованы классами, которые реализуют эти интерфейсы. Использование интерфейсов позволяет создавать более гибкий и расширяемый код, так как классы могут реализовывать несколько интерфейсов, обеспечивая таким образом множественное наследование поведения. Преимущества и недостатки |
Сообщ.
#7
,
|
|
|
Majestio, так, вернулся, ещё раз перечитал, и у меня главный вопрос: ты чего хотел-то? Если учебник по терминам, то сорри, это ж азы, и вряд ли у тебя с этим проблемы. Если какое это отношение имеет к Плюсам, так ответ очевиден: прямое, т.к. Плюсы поддерживают парадигму ООП. Если как это в Плюсах реализуется, то это опять же учебник, только по языку, а не программированию.
|
Сообщ.
#8
,
|
|
|
На самом деле, мне интересны "Преимущества и недостатки", т.е. способы применения. Когда что лучше, когда хуже, и почему. Чисто академический интерес.
|
Сообщ.
#9
,
|
|
|
Хм. Ну, GPT в целом тебя не обманул. Но есть нюанс: описал скорее следствия преимуществ и недостатков, нежели сами преимущества и недостатки.
Важно не забывать, что наследование создаёт сильную зависимость нового класса от предков. Любые изменения в них отражаются на производном классе, т.к. они являются его частью. Это недостаток, ибо чем слабее связи между сущностями, тем проще управлять комплексом в целом, однако – гораздо реже, чем поначалу кажется студентам, впрочем – бывает так, что выгода с лихвой этот недостаток перебивает. Типичная ситуация использования: когда целью изначально был этот самый производный класс, и его деление на базовые оказалось просто удобным средством его декомпозиции на подобъекты. Ирония в том, что в таких случаях наследованию нет необходимости быть публичным, т.к. базовые классы не предназначаются для использования отдельно от производного и даже могут не иметь публичного интерфейса вовсе. Хороший пример – классы (не ассоциативных) контейнеров std::vector<>, std::deque<>, std::basic_string. Им всем нужен примерно одинаковый функционал по управлению своим хранилищем, который не привязан к свойствам конкретных объектов, которые эти контейнеры будут хранить. Нужно уметь проверять доступное место в хранилище, нужно уметь его расширять/уменьшать, нужно уметь его перераспределять без потери уже имеющейся в нём информации. Почему бы не выделить этот функционал в отдельную сущность? Контейнеры его далее смогут просто использовать. Да, тут будет иметь место устранение дублирования и повторное использование, но даже притом, что если бы был только один тип контейнера, std::vector<>, например, то это всё равно выгодно, так как мы декомпозировали сложный объект на два простых, где каждый занимается своим делом и не лезет в дела другого. Ожин занимается хранилищем, другой push_back()чит, insert()ит, []сит итд. Так что я легко вижу тут некий std::storage, который приватно наследуется и std::vector<>, и std::deque<>, и std::basic_string. При этом std::storage запросто может оказаться без публичного интерфейса вовсе, т.к. бесполезен вне этих (ну или нами самописных подобных, мало ли) контейнеров.Второе свойство наследования – расширяемость – тоже студентами зачастую понимается превратно. Типа, у-у-у! как здорово, у нас есть std::string, а давайте добавим ему связи с std::locale и научим case insensitive и up/down cases, и фигачат template <typename Ch, typename Tr = std::char_traits<Ch>, typename Al = std::allocator<Ch>> class basic_locale_string: public std::basic_string<Ch, Tr, Al>, public std::locale; to be continued |
Сообщ.
#10
,
|
|
|
continue is here Пример: паттерн "стратегия" в одной из своих возможных вариантах архитектуры. Ну, кто читал Александреску, те понимают хорошо. При этом наследование тут в общем-то не требуется, хотя в зависимости от конкретики оно может быть полезно. В std, например, таковым паттерном является концепция аллокаторов, и наследования там не применяется. (Впрочем, строгого запрета на это нет, просто оно не нужно.)
В качестве примера реализации композиции с наследованием а-ля Александреску могу привести свою библиотечку сокетов. Там что-то вроде: // служебные классы template <int = AF_INET> class SocketBase; template <int = AF_INET> class ConnectBase; template <class, int = AF_INET> class SocketRead; template <int = AF_INET> class SocketWrite; template <int = AF_INET> class SocketServer; template <int = AF_INET> class SocketClient; template <int = AF_INET> class SocketUDP; template <int = AF_INET> class BroadcastWrite; // классы для использования template <int = AF_INET> class BroadcastServer; // слушатель броадкастов template <int = AF_INET> class BroadcastClient; // рассыльщик броадкастов template <int = AF_INET> class SocketServerUDP; // читатель датаграм template <int = AF_INET> class SocketClientUDP; // писатель датаграм template <int = AF_INET> class SocketServerTCP; // точка подключения для серверов template <int = AF_INET> class SocketClientTCP; // клиент для подключения к серверу /* Служебные классы. Не имеют публичного интерфейса, в их задачи входит предоставление конкретного функционала, запрашиваемого теми или иными аспектами функциональности сокетов. */ /* Базовый класс сокета. RAII над сокетом. Занимается только созданием, удалением и хранением состояния последней операции */ template <int TYPE> class SocketBase {/*...*/}; /* Базовый класс соединения. Занимается только подключением к серверу. Броадкаст также возможен. */ template <> class ConnectBase<AF_INET>: virtual public SocketBase<AF_INET> {/*...*/}; /* Класс стратегии чтения из сокета. Приём на конкретном сконнекченном сокете. */ template <int TYPE> class SocketReadRaw: virtual public SocketBase<TYPE> {/*...*/}; /* Класс стратегии чтения из сокета. Приём по броадкасту. Сохраняет последнего клиента. */ template <int TYPE> class BroadcastReadRaw: virtual public SocketBase<TYPE> {/*...*/}; /* Сокет для чтения. Raw является стратегией чтения (непосредственный клиент/броадкаст). */ template <class Raw, int TYPE> class SocketRead: virtual public SocketBase<TYPE>, public Raw {/*...*/}; /* Сокет для записи. Занимается только записью в сокет. */ template <int TYPE> class SocketWrite: virtual public SocketBase<TYPE> {/*...*/}; /* Групповой сокет для записи. Предназначен для броадкастов. Броадкасты не маршрутизируются. */ template <> class BroadcastWrite<AF_INET>: virtual public SocketBase<AF_INET> {/*...*/}; /* Сокет для серверов. Ориентирован на приём данных. Собирается из классов сокета, коннекта и чтения с политикой коннекта. */ template <int TYPE> class SocketServer: virtual public ConnectBase< TYPE>, public SocketRead <SocketReadRaw<TYPE>, TYPE> {/*...*/}; /* Сокет для клиентов. Ориентирован на передачу данных конкретному серверу. Собирается из классов сокета и конкретной записи. */ template <> class SocketClient<AF_INET>: public SocketWrite<AF_INET> {/*...*/}; /* Базовый класс для UDP сокетов. Обрабатывает только свойства сокета. Аналогичный базовый для TCP сокетов не нужен, ибо будет просто пустой. */ template <int TYPE> class SocketUDP: virtual public SocketBase<TYPE> {/*...*/}; /* Конкретные классы сокетов. Собираются из служебных классов по принципу требуемой функциональности. */ /* Сокет UDP для серверов. Умеет только читать. */ template <int TYPE> class SocketServerUDP: public SocketUDP<TYPE>, public SocketServer<TYPE> {/*...*/}; /* Сокет UDP для клиентов. Умеет только писать. */ template <int TYPE> class SocketClientUDP: public SocketUDP<TYPE>, public SocketClient<TYPE> {/*...*/}; /* Групповой сокет для клиентов. Умеет только писать, но сразу всем. */ template <int TYPE> class BroadcastClient: public SocketUDP<TYPE>, public BroadcastWrite<TYPE> {/*...*/}; /* Групповой сокет для серверов. Умеет только читать, зато ото всех. */ template <int TYPE> class BroadcastServer: virtual public ConnectBase<TYPE>, public SocketUDP <TYPE>, public SocketRead <BroadcastReadRaw<TYPE>, TYPE> {/*...*/}; /* Сокет коннекта для серверов. Умеет и читать и писать, ожидает от клиентов подключений. */ template <int TYPE> class SocketServerTCP: public SocketServer<TYPE>, public SocketWrite<TYPE>{}; /* Сокет коннекта для клиентов. Умеет и читать и писать, подключается к серверу как клиент. */ template <int TYPE> class SocketClientTCP: public SocketClient<TYPE>, public SocketRead<SocketReadRaw<TYPE>, TYPE> {/*...*/}; /* Контейнер сокетов для групповых опросов с таймаутом. Реализован через select() как единственный вариант реализации таймаутов. */ class FDS {/*...*/}; Композиция в том виде, как она представляется в определении, ...смущает, скажем так. Слишком частное определение. Я могу себе представить композицию в виде атрибутов класса, но извне, т.е. на уровне интерфейса класса, это композицией не особо выглядит. to be continued more |
Сообщ.
#11
,
|
|
|
It is continue При желании реализовать настоящую агрегацию раз плюнуть. Типичный пример: архитектура потоков ввода-вывода, принимающих буферизирующий std::basic_streambuf<> в конструкторах std::basic_(i/o)stream<>. Типа создаём свой socket_buf на базе Sockets::SocketClientUDP<> производным от std::stream_buf<>, передаём в конструктор std::ostream и вуаля, пишем в сокет обычными operator<<. Вообще, в Плюсах агрегация часто соседствует с полиморфизмом, т.к. полагается на позднее связывание вкупе с фабриками объектов. Не знаю, мне практически никогда не требовалось. (Да, почти.)
Что же до агрегации в стиле Плюсов, то это более чем обычное дело. Композицией это не называют, потому как выше уже объяснена неоднозначность этого термина. Зато это прекрасная альтернатива наследованию, когда оно избыточно, см. пост о наследовании. Там мы лишаемся недостатка сильной связи, т.к. агрегат с агрегирующим классом связан слабо, только через свой интерфейс. Также от агрегирующего не требуется вести себя подобно агрегату, и при этом никто не мешает завести атрибут агрегата, чтобы передать куда надо. Есть только некая проблема с делегированием методов, но она категорически несложно решается препроцессором , а для особо грамотных и без оного, на шаблонах с несложным метакодом, при этом "бесплатным" бонусом получаем управляемость делегирования, а не тупо identity forward, т.к. при необходимости identity легко и точечно специализируется. К слову, если кто из неплюсовиков или не очень опытных плюсовиков не понимают, нафика нам вся эта мета. Нет, не потому что можем, а потому, что мы, в отличие от них, понимаем его потенциал и видим, где он полезен. Внезапно – много где и чаще, чем им кажется. Возможность отделить время жизни агрегата от времени жизни его контейнера, равно как и возможность подмены агрегатов в ран-тайм, мне кажется сомнительным преимуществом. Ну, второе ещё куда ни шло, но и это скорее всего приведёт к тому, что контейнер всё равно придётся перезапустить. Пусть и без пересоздания, но всяко с переинициализацией. Что же до первого, то позвольте вопрос: если ли смысл в существовании атрибута отдельно от его объекта? Ну вот создал я socket_buf. И что? Что я сним буду делать вне std::ostream? Не, ну архитектура потоков делает это возможным и даже относительно осмысленным, но часто ли std::stream_bufы используются сами по себе? Можно порыть статистику в гугле, и мне кажется, примерный результат я смогу предсказать. Агрегация в том своём определении ИМХО является архитектурным анахронизмом. С нею можно жить, как и с malloc() + init() вместо operator new. Но надо ли? Впрочем, совсем бесполезной она всё ж не является. Те же потоки и std::locale как пример, однако ж и там при std::basic_ios<>::imbue() следует некислая такая переинициализация, выливающаяся в приличный оверхед. finalization will asap |
Сообщ.
#12
,
|
|
|
the finalization Главное преимущество интерфейсов – абсолютное абстрагирование от реализации – одновременно и главный их недостаток: после публикации их категорически нельзя менять. Ну не то, чтобы совсем нельзя, но любая правка интерфейса влечёт уйму работы по регрешну и рефакторингу, т.е. правки интерфейсов очень дороги. Ситуация как с наследованием, сильная связь интерфейса с его реализациями налицо. (Не удивительно поэтому, что реализация интерфейса практически повсеместно является публичным производным классом от класса, этот интерфейс документирующего.) Не ну а как иначе-то, если они сильно связаны. Та и вообще, ровно для того, чтобы быть неизменными, интерфейсы и создаются, ибо ну а зачем они иначе нужны вообще. Так что это не недостаток, нет, я не так выразился.
Недостаток в том, что планировать надо в самом начале, и неудачно спланировать раз плюнуть, вот и приходится иногда перепланировать. А чтобы не было неудачно, нужно много шишек, причём через опыт, и желательно собственный, т.к. чужой усваивается плохо, а значит и книжки плохо помогают. Лично мне осознать помог лишь один случай, когда пришлось двухнедельную работу просто удалить и всё начать заново. Ох, я ходил курить раза три, панелька "Аре йоу суре ту делит зис перфект анд вондерфул директори?" в файловом менеджере висела минут сорок, всё не решался нажать ок. Короче, это всё лирика. Хотя и не без пользы, за недостатки ж, как-никак. Самый прямой вариант документирования интерфейсов наличествует в практически любом языке. Interface ICoolProtocol { и вперёд вплоть до }; пишем А потому, что: Абстрактные классы не являются интерфейсами в прямом смысле, но они легко под них мимикрируют. Их экземпляры нельзя создавать сами по себе, даже в составе агрегатов, от них обязательно нужно унаследоваться и создавать уже производные экземпляры. Они документируют интерфейс взаимодействия и не содержат никаких атрибутов. Все вот такие вот =0 чистые методы обязательно перекрываются, а если не все, у нас всё равно получится абстрактный класс, чьи непосредственные экземпляры компилятор создать не позволит. Наследуясь от них виртуально, мы получаем форсирование совмещения экземпляров этого класса в производном вне зависимости от того, сколько раз прямо или опосредовано он встретится в списке базовых. Т.е. всё, что нужно, в них есть. Но в них есть и кое-что ещё, на что интерфейсы неспособны. Например, чистые методы можно реализовать, предоставив т.с. реализацию-по-умолчанию. (Это всё равно не позволит создавать экземпляры таких классов иначе чем в составе реализующих производных классах, ибо =0 никуда не делось.) Они могут иметь атрибуты сиречь поля и конструкторы. Они обязаны наследоваться, но не обязаны публично и/или виртуально. Ну т.е. они просто классы. Зачем оно всё надо? Вообще-то абстрактные классы ИМХО полезны чаще, чем интерфейсы. В конце концов они сами могут производными других классов, не обязательно абстрактных даже, что позволяет использовать их поверх отлаженного легаси. И да, можно реализовать только часть интерфейса, и дореализовать остальное в более верхнем производном. Зачем? А вот в В заключение могу добавить, что работа с интерфейсами в Плюсах вполне на уровне. Легко можно проверить, поддерживает ли некий класс тот или иной интерфейс, просто сделав ему dynamic_cast<>. В тех же Дельфях, например, AS не даёт той же гибкости, так как не способен на перекрёстные приведения, так что там без API операционной системы, QueryInterface(), например, такого не сделать. Нужно лишь, чтобы класс был полиморфным, ну а куда он денется, коли методы виртуальные. Так, если у тебя коллекция QT-объектов с кучей всяко-разных кастомных пользовательских расширений, и тебе надо их сериализировать, то dynamic_cast<> к каждой кастомизации легко выявит те, которые требуют дополнительных усилий. Так что я не знаю, когда может быть полезным частичная реализация интерфейса. Теоретически это должно быть плохой практикой, ибо видя перед собой ICoolProtocol в базах, я получу ложное впечатление, что он реализован. Но если вдруг такое нужно... Всё ж лучше разделить исходный интерфейс на части и тогда уж реализовывать их, но каждый полностью. Принцип неизменности интерфейса требует, что чтобы что-то в нём поменять, нужно заводить новый интерфейс. Если изменения касаются лишь добавления функционала, то пусть даже просто как производный от исходного. Типа ICoolProtocol2: virtual public ICoolProtocol. Если же изменения касаются не только добавления, но и подмены имеющегося, то имеет смысл переосмыслить старый функционал и перереализовать его в форме нового, и тогда в реализации-по-умолчанию сделать dynamic_cast<> к новому интерфейсу и заюзать его. В итоге реализации ICoolProtocol2 смогут просто не напрягаться депрекейтед ICoolProtocol. Такое невозможно в интерфейсах, но легко делается на абстрактных классах. Ну а что касается использования, то просто принимаем ICoolProtocol*, если нам достаточно ICoolProtocol, и тогда реализация и ICoolProtocol, и ICoolProtocol2 легко проходят derived to base convertion. А если недостаточно, то явно принимаем ICoolProtocol2*, и тогда вызовы с реализацией только ICoolProtocol не пройдут компиляцию. Это всё касается динамического полиморфизма. Если есть желание поговорить за статический, то это выходит за рамки того определения. Ну не совсем, но выйдет, когда зайдёт разговор. Fatality! бдыщь |
Сообщ.
#13
,
|
|
|
Qraizer, большое спасибо, что не поленился так объемно, и так подробно расписать! Кому как эта тема зайдет, а я заинтересовался жЫрнотой Qt. Да, этот фрэймворк для UI я пользую уже давно в 100% случаях. Но его жЫрнота меня расстраивает. Захотелось его как-то оценить с точки зрения современных подходов проектирования. Давно как-то попался интересный альтернативный проект для создания GUI - Nana (мануальчик). Мои эксперименты показали, что при использовании Nana, размер собранного проекта примерно в 20-25 раз меньше, чем тоже самое можно получить с помощью Qt. Уверен, что и wxWidgets по количеству жЫра очень близок к Qt. Чтобы избежать всяких кривотолков - я использовал строго статическую линковку, с последующим strip исполняемых модулей.
Да, у всех перечисленных выше проектов своя история и свои скелеты в шкафу. Которые тянутся от релиза к релизу. Не знаю на сколько последняя версия Qt требует последних стандартов С++, но точно помню, что свои разработки они начали вести еще задолго до появления С++11. Оттуда их реализация сигналов-слотов c использованием мета-объектной информации и использовании для этого MOC. А это уже не С++, а С++ с костылями. В Nana такого нет, ибо стартанул проект с обязательным требованием С++11 и выше. Там взаимодействие строится на лямбдах с замыканиями. Может визуально это смотрится более громоздко, чем современный синтаксис connect от Qt, но не требует дополнительного мета-барахла. Ну это так ... немного оффтопик. Ну а стартанул я эту тему в разделе С++ т.к. с этим ЯП я на "ты", ну или почти. А вот два других ЯП, которые меня заинтересовали (Rust и Dart) у меня пока в стадии знакомства. Пытаюсь спроецировать свои знания из С++ на них. Но, ясный перец, языки разные, где-то совсем разные. Поэтому решил, скажем так, рассматривать их с более высокой ступеньки абстракции - с подходов к проектированию. А иначе совсем туман. Вощем, твои ответы будем почитать наверное еще много раз. Как говорят "диавол кроется в мелочах", а они не все и не всегда в зоне внимания. ЗЫ: Тему считаю раскрытой, но топик не закрываю - мож кто еще решит высказаться. Но, в целом, я доволен! |
Сообщ.
#14
,
|
|
|
Цитата Мое скромное мнение:Да, этот фрэймворк для UI я пользую уже давно в 100% случаях Скрытый текст Не хочу разводить холивар, считаю что разные способы имеют право быть. Несколько лет назад я пытался установить и настроить Qt, но что-то пошло не так, надоело мне возиться (я не программист и решаю прикладные задачки или пишу игры, но все тонкости современных сред не знаю) и взглянул в сторону написать User Interface на чистом WinAPI и так мне понравилось, что до сих пор пишу на чистом Си и WinAPI. Ну и как бонус программа очень малого размера и быстрая. Правда нет всех вожможностей как у профессиональных контролов из Qt, но мои потребности такой способ удовлетворяет. // Как вывод, (опять же это только мое мнение) применение той или иной технологии/парадигмы/языка зависит в первую очередь от задачи. Что-то проще делать на одном языке программирования, а что-то на другом. Цитата А вот два других ЯП, которые меня заинтересовали (Rust и Dart) Если заинтересовали значит, Вас же что-то не устраивает в С++ или ищете возможности делать то же самое с меньшими трудозатратами, иначе зачем ? Ведь можно потратить время (которое вы потратите на изучение) на то, чтобы сделать что-то полезное уже знакомым инструментом. |
Сообщ.
#15
,
|
|
|
H g, вариант.
Скрытый текст Ты серьёзно увеличишь производительность труда и качество результата, если то же самое будешь делать на с++. Фактически, такое мероприятие приведёт к созданию библиотеки классов, которые можно будет использовать многократно. При этом нет необходимости делать все возможные классы сразу - изготавливать их можно по мере необходимости. Библиотека будет расти постепенно и не потребует каких-то запредельных трудозатрат. |