LINUX.ORG.RU

Функциональщина на C++

 , ,


0

5

По мотивам: Си с классами

бери с++20 с концепциями, корутинами и ренжами. игнорируй всё из с++17, сфинае, не пиши упоротые шаблоны, вообще шаблоны старайся не писать, и всё будет ок.
концепции уже вроде работают, ренжи тоже есть, корутины ещё не подъехали, но в будущем пригодятся, генераторы там всякие, всё такое. ещё будет проще потом перелезть на экзекюторы и т.д. потом ещё модули затащишь.

Как эффективно учиться? (комментарий)

Я недавно всерьез собрался найти где-то гайды по крестам, которых был бы описан какой-то цельный подход к писанию на новых крестах с полным игнорированием неудачного наследования старых крестов, но с удивлением обнаружил, что таких нет или почти нет — можно найти отдельные подсказки, которые придется долго собирать воедино, а самому мне это делать тяжеловато, потому что кресты я только начинаю осваивать

Конкретно мне интересен функционально-процедурный подход к написанию кода на крестах, что-то похожее на Rust, только без абсурдной помешанности на безопасности памяти, так сказать «си с плюшками», но совсем НЕ «си с классами», как было в упомянутом треде. Для примера: Qt и UE — это примеры плохой архитектуры в данном контексте. Например, fstream — это плохая реализация файловых операций, поскольку скатывается в классы и исключения, в ней даже нельзя без исключений получить конкретную ошибку файловых операций.

Итак: какие есть конкретные хорошо проработанные приемы и библиотеки для писания на крестах в функционально-процедурном стиле?

★★★★

а собственно к чему эти поедания кактуса? зарядка для хвоста, или попытка доказать, что ты один функциональный д’Артаньян, а остальные …?

весь С++ в первую очередь про ООП, просто синтаксис крестов не исключает возможности писать в функциональном стиле, иначе ты обречен на полный отказ от стандартной библиотеки

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

а собственно к чему эти поедания кактуса? зарядка для хвоста, или попытка доказать, что ты один функциональный д’Артаньян, а остальные …?

Это попытка сделать из говна конфетку. Си до сих пор не умер именно потому, что модель писания алгоритмов на классах и исключениях в большинстве случаев значительно менее эффективна, чем функционально-продедурное программирование. Особенно если речь идет про модификацию программы. Написать один раз и забыть на классах можно так же эффективно, как и на процедурах — во весь рост убогость класс-ориентированной архитектуры проявляется именно при дописывании и переписывании.

весь С++ в первую очередь про ООП, просто синтаксис крестов не исключает возможности писать в функциональном стиле, иначе ты обречен на полный отказ от стандартной библиотеки

На самом деле стандартная либа крестов так печальна, что даже для целей класс-ориентированного программирования упомянутые в исходном сообщениие Qt и UE переписали стандартную библиотеку с нуля или почти с нуля.

byko3y ★★★★
() автор топика
Ответ на: комментарий от EugeneBas

ООП там давно сбоку припека, особенно не нужны развесистые иерархии и мучения с наследоваеием, которыми не так давно многие двуногие безрогие на плюсах активно страдали

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

сбоку относительно чего? первой целью Страуструпа при создании плюсов было именно добавление ООП

особенно не нужны развесистые иерархии и мучения с наследоваеием

проблемы неосиляторов, они такие

EugeneBas ★★
()
Последнее исправление: EugeneBas (всего исправлений: 1)
Ответ на: комментарий от EugeneBas

проблемы неосиляторов, они такие

Скорее, проблемы компиляторов-неосиляторов, которые не давали эффективный dynamic_cast. Если накостылять свой dynamic_cast, то развесистые иерархии стразу становятся полезными.

aist1 ★★★
()
Ответ на: комментарий от EugeneBas

признак плохого дизайна кода

Кто-то где-то что-то очень авторитетно сказал про какой-то свой печальный опыт. Потом все подхватили. Если рассматривать наследование как способ кодирования данных, то всё начинает выглядеть не таким уж и плохим дизайном.

так-то он вообще не нужен

Без dynamic_cast не получится сделать интерфейсы, которые очень даже хорошо используются в той же Java.

aist1 ★★★
()
Ответ на: комментарий от aist1

//интерфейс в стиле Java
struct MyInterface {
   virtual void doSomething() = 0;
};

struct MyImplementation: MyInterface {
   MyImplementation() = default;
   void doSomething() override {
       std::cout << "hello" << std::endl;
   }
}

MyInterface* i = new MyImplementation();

в каком месте тут понадобился динамик каст?

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

Добавь множественное виртуальное наследование и необходимость instanceof() и даункастига. Как тут без dynamic_cast?

aist1 ★★★
()
Ответ на: комментарий от EugeneBas

первой целью Страуструпа при создании плюсов было именно добавление ООП

И что? А первой целью твоего бати было весело побухать и погулять — какая разница, если результат не имеет к этому почти никакого отношения.

byko3y ★★★★
() автор топика
Ответ на: комментарий от aist1

ну добавлю еще в иерархию классов, отнаследую часть виртуально, а что изменится? наследник всегда кастится к предку без приседаний, а если тебе нужен dynamic_cast, то ты хочешь кастить предка к насленику, т.е. ты очевидно неправильно дизайнишь свой код

EugeneBas ★★
()
Ответ на: комментарий от byko3y

а то, что написание функционального кода автор допускал, а не имел приоритетной целью, чувствуешь разницу?

поэтому тебе выше и объясняли, что на плюсах технически возможно писать функциональных код, но на практике эти попытки выливаются в нечитаемое гуано

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

проблемы неосиляторов, они такие

Проблемы неосиляторов композиции и прослушавших что Степанов про ООП говорил да, безусловно такие.

А плюсы давно переросли ООП и стали немножко мультипарадигменными, о чем говорил Страуструп, и что опять прослушали неосиляторы :)

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

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

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

т.е. ты очевидно неправильно дизайнишь свой код

С чего ты взял? Таких вещей полно в компонентном программировании.

aist1 ★★★
()
Ответ на: комментарий от EugeneBas

если тебе нужен dynamic_cast, то ты хочешь кастить предка к насленику, т.е. ты очевидно неправильно дизайнишь свой код

В стандартной библиотеке C++ dynamic_cast используется в 4 местах:

  1. https://en.cppreference.com/w/cpp/error/rethrow_if_nested

  2. (dynamic_pointer_cast) https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

  3. https://en.cppreference.com/w/cpp/locale/use_facet (в локали хранятся locale::facet*, а нужно вернуть Facet&)

  4. https://en.cppreference.com/w/cpp/io/manip/emit_on_flush

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Type erasure же. Boost Any делает даункастинг без dynamic_cast, но это просто очень частный случай type erasure: положил-достал.

aist1 ★★★
()
Ответ на: комментарий от EugeneBas

а если тебе нужен dynamic_cast, то ты хочешь кастить предка к насленику, т.е. ты очевидно неправильно дизайнишь свой код

В прикладном коде даункасты – действительно признак говнокода. Но когда я пилил фреймворки на жаве/скале, мне изредка требовалось кастить к подклассу. Не помню при каких обстоятельствах, но отложилось в памяти, что это именно специфика фреймворкового кода, которому на вход подсовываются произвольные подклассы его – фреймворковых – классов.

На вскидку придумался такой сценарий (не помню, реальный или нет): абстрактный фреймворковый класс веб-страницы – Page; фреймворковый подкласс со спец-обработкой – ErrorPage; все остальные страницы. На плюсах я не использую RTTI и поэтому завёл бы какой-нибудь флажок/enum Page::getType(); а в JVM информация о типах в рантайме есть всегда, так что проще проверить (page instanceof ErrorPage) чем дополнительные сущности городить.

Ну и наконец есть довольно забавный пример – когда внешне вроде не даункаст, а по сути – он самый, родимый:

try {
    ...
} catch (std::system_error& e) {
    ...
} catch (ещё_какой_нибудь_подкласс_std_exception& e) {
    ...
} catch (std::exception& e) {
    ...
}
dimgel ★★★★★
()
Ответ на: комментарий от aist1

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

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

Давай, я всё же попрошу тебя раскрыть и обосновать рационально свой тезис «ты очевидно неправильно дизайнишь свой код». Но, поскольку конкретно этот тезис сейчас в такой форме, которую сложно отстаивать или опровергать, я предлагаю его уточнить.

Я предлагаю следующие варианты:

1. Любой «неправильный дизайн» будет содержать dynamic_cast (необходимость).

2. Наличие dynamic_cast делает дизайн «неправильным» (достаточность).

3. Некоторые «направильные» дизайны содержат dynamic_cast.

4. Нектоторые дизайны, содержащие dynamic_cast, «неправильны».

5. Наличие dynamic_cast хорошо коррелирует (условно, более 0.75) c «неправильностью» дизайна (статистический критерий).

Выбери наиболее близкий вариант (1-5) или предложи свой. потом раскроем понятие «неправильный».

Аргументы вида «dynamic_cast медленных, поэтому всё, что его требует, стоит избегать на уровне дизайна» не рассматриваем, потому что это совсем другой тезис будет.

aist1 ★★★
()
Последнее исправление: aist1 (всего исправлений: 2)
Ответ на: комментарий от dimgel

мое мнение, что необходимость даункаста - это признак непродуманного интерфейса родителя даже во фреймворке

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

The difference between theory and practice is that in theory there’s no difference between theory and practice, but in practice there is.

Кстати, там где-то выше Аист упомянул даункасты в контексте type erasure – дык вот помню в каком проекте (lightweight ORM на скале с компиляцией scala-функций в хранимки – всё на макросах, разумеется), но убей бог не помню как и с какого боку, но я вляпывался в даункасты ровно из-за type erasure. Возможно, при трансформациях AST.

О, кстати! Pattern matching над типами – тоже по сути instanceof + даункасты. И используется вовсю и повсеместно, далеко не только при преобразованиях AST.

result match {
    case OK(data) => { ... }
    case Error(message) => { ... }
}
dimgel ★★★★★
()
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от dimgel

Всё с instanceof проще. Информацию о типе приходится тянуть по цепочке использования объекта. Где-то она будет лишней, где-то нет. В случае С++ приходится или мономорфизировать код (быстрее, но больше кода), или делать type erasure, и платить циклами за диспетчерезацию в рантайме.

Чем меньше type erasure, тем больше специализации кода. Каждый случай тут отдельный.

aist1 ★★★
()
Ответ на: комментарий от aist1

А, вон ты в каком смысле. Эта дилемма очевидна, но я среагировал на термин: в терминологии java «type erasure» == «runtime type erasure», т.е. в рантайме JVM не знает типопараметры generic-классов; остаётся чистый LSP, за который по сути здесь ратует @EugeneBas (так-то в теории ратует правильно, LSP в ООП – вещь ключевая).

А вот в .NET CLR, «runtime type erasure» нету, там рантайм знает типопараметры generic-классов, в т.ч. например коллекций, в результате никакими кастами ты не добавишь в массив воронов письменный стол. А в жаве – раз плюнуть.

dimgel ★★★★★
()
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от dimgel

Нене. Мы должны разделять type erasure, как инструмент дизайна модели данных и type erasure, предоставляемый самой платформой в рамках её собственных абстракций. Тут можно сказать, например, что type erasure, встроенный в платформу, — это хороший type erasure. Потому что умные люди его благословили и поддерживают «правильно» на уровне платформы. Я согласился бы с тезисом, что dymaic_cast плохо реализован и его стоит избегать там, где критична скорость и (изредка) переносимость. Сам так делаю.

Но ты можешь сделать даункастинг своими средствами, которые не будут иметь этих проблем.

так-то в теории ратует правильно, LSP в ООП – вещь ключевая

Дело в том, что LSP — это частный случай других, более выскоуровневых, принципов. Которые тоже можно раскрыть в конструкции С++ и как-то отображать в объектную модель. Например, объекты С++ — это конечные автоматы. А можно их рассматривать как коды (в смысле теории информации). Т.е. иерархия становится способом кодирования информации в рамках средств языка. Много чего можно, и это — нормально. Хотя оно не обязательно будет идеально ложится на сам язык))

aist1 ★★★
()
Ответ на: комментарий от aist1

Дело в том, что LSP — это частный случай других, более выскоуровневых, принципов.

Недопонял. Каких именно? Форсировать бизнес-правила через систему типов умею, но как раз здесь-то dynamic_cast смертелен: объезжаем на хромой козе систему типов ==> объезжаем бизнес-правила.

UPD. Короче для быдлокодеров-практиков, плиз. Я не являюсь теоретически подкованным ни в теории информации, ни в какой-либо другой теории (кроме азов матана).

dimgel ★★★★★
()
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от fsb4000

по пунктам:

  1. rethrow_if_nested - костыль, т.к. решили не менять изначальный интерфейс базового класса exception, по-хорошему функциональность std::nested_exception надо было вносить в базовый класс
  2. dynamic_pointer_cast - не считается, т.к. просто реализация dynamic_cast для умных указателей
  3. std::use_facet - как раз пример плохого дизайна, фасеты как таковые не нужны, интерфейс локали должен содержать методы фасетов, а реализации должны их переопределять, тогда и касты не будут нужны
  4. под рукой имплементации с++20 нет, чтобы посмотреть что там

но резюме такое - неубедительно

EugeneBas ★★
()
Ответ на: комментарий от aist1

5. Наличие dynamic_cast хорошо коррелирует (условно, более 0.75) c «неправильностью» дизайна (статистический критерий)

Я голосую за 5. Динамическое приведение типов редко нужно, но иногда оно таки нужно. Очень иногда. Правда, примерно то же самое можно реализовать на каких-нибудь интерфейсах, и тогда полностью убрать динамическое приведение.

byko3y ★★★★
() автор топика
Ответ на: комментарий от dimgel

Недопонял. Каких именно?

Теории алгоритмической информации, например. Там сложный развесистый матан, забей. Просто поверь мне на слово :)

Форсировать бизнес-правила через систему типов умею

Дело в том, что систему типов еще надо вывести индуктивно, и это ооочень нетривиальная в вычислительном плане задача. Одно дело — писать линейный код, и другое — полную и непротиворечивую систему правил. Да еще чтобы вывод в ней был за приемлемое время.

Поэтому все практичные системы типов — лишь аппроксимации своих идеальных задумок. И вот разницу между идеальной системой типов и её аппроксимацией приходится костылять на уровне частных случаев и этих самых даункастов, рассматриваемых как обход системы типов.

Требовать, что нужно писать «правильно и с первого раза» (сразу писать правильный дизайн системы типов) — это, как минимум, не понимать, с чем имеешь дело.

aist1 ★★★
()
Ответ на: комментарий от aist1

Там сложный развесистый …, забей.

Ок, верю. :)

матан

Не верю. :) Матан – это что-то про бесконечно-малые. :) А весь этот ваш IT и CS – на конечных величинах. Скукотища…

dimgel ★★★★★
()
Ответ на: комментарий от EugeneBas

ну вот это единственная вещь, где в реализации dynamic_cast хоть как-то оправдан

Ну слава тебе, Железный Господин! Разобрались)))

aist1 ★★★
()
Ответ на: комментарий от dimgel

Не верю. :)

Хорошо, «матан». В переносном смысле, как общее название математики. Там теория вероятностей, много её, машины Тьюринга, очень много их всяких, опять же. Универсальные языки описаний на основе МТ и куча всяких забавных теорем про вот это вот всё. Мемория, кстати, на AIT (теории алгоритмической информации) построена, так как использует соответствующие принципы кодирования информации.

aist1 ★★★
()
Ответ на: комментарий от aist1

dynamic_cast по своей природе костыль, поэтому я предложу свой вариант: любой дизайн, кроме type erasure, спроектирован неправильно

и неправильность не в том, что медленно или еще как-то не так исполняется, в том, что при моделировании что-то не учли и неправильно выбрали абстракции

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

в том, что при моделировании что-то не учли и неправильно выбрали абстракции

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

aist1 ★★★
()
Последнее исправление: aist1 (всего исправлений: 1)
Ответ на: комментарий от EugeneBas
  1. под рукой имплементации с++20 нет, чтобы посмотреть что там

https://github.com/microsoft/STL/commit/834baa6acacb8325367e255b0728c582c7e443ff

или

https://github.com/gcc-mirror/gcc/commit/ecba8547dd398ad4b627756013dbd22be417d4da

LLVM libc++ пока не реализовали…

Да я просто grepом прошёлся по stl, посмотреть сколько там dynamic_castов найдёт :)

4 штуки, не так много…

fsb4000 ★★★★★
()
Ответ на: комментарий от aist1

Ну мы вообще живем в неидеальном мире, где горят сроки, существуют требования поддержания обратной совместимости любой ценой и т.п. Поэтому да, такой код всегда будет существовать, но к идеалу надо стремиться.

EugeneBas ★★
()
Ответ на: комментарий от fsb4000

ну собственно то же самое, не совсем понятно про что речь в дифе, но явно в родительском классе не хватает какого-то интерфейса, поэтому приходится смотреть, не принадлежит ли наследник к определенному типу

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

но к идеалу надо стремиться.

Есть другая мудрость: не браться за физически нерешаемые задачи.

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

Но мы друг-друга поняли, спасибо! :)

aist1 ★★★
()
Ответ на: комментарий от EugeneBas

В С++20 добавили thread_safe обёртки над потоками.

https://en.cppreference.com/w/cpp/io/basic_osyncstream

Они обычно передают данные в деструкторе. Или когда делают emit. (захватывают mutex и безопасно передают данные в родительский буфер)

fflush они откладывают.

Но это можно изменить.

synced_out.rdbuf()->set_emit_on_sync(true);

Но эти basic_osyncstream наследуются от обычных basic_ostream.

И их можно передавать в функции, которые ждут basic_ostream.

Но у basic_ostream.rdbuf() нет метода set_emit_on_sync.

Поэтому есть манипуляторы:

out << std::emit_on_flush;
out << std::noemit_on_flush

Они не делают ничего, если это обычный basic_ostream.

И делают

out.rdbuf()->set_emit_on_sync(true);
out.rdbuf()->set_emit_on_sync(false);

если out это basic_osyncstream.

Вот для этого и нужен dynamic_cast.

Но да.

Возможно пользователю просто нужно делать две функции.

отдельно для basic_ostream (считать, что это не thread_safe)

и отдельно для basic_osyncstream.

Тогда эти манипуляторы будут не нужны…

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от EugeneBas

иначе ты обречен на полный отказ от стандартной библиотеки

Не велика потеря. Там мусор по большей части. Крупные проекты пишут свои аналоги.

X512 ★★★★★
()
Ответ на: комментарий от X512

Там мусор по большей части

Там был мусор. Сейчас качество растет. Но всё равно библиотеке еще развиваться и развиваться.

aist1 ★★★
()
Ответ на: комментарий от X512

Части стандартной библиотеки С++ не требуется C++ рантайм.

bit
cassert
cctype
cerrno
cfenv
cfloat
cinttypes
climits
clocale
compare
concepts
coroutine
csetjmp
csignal
cstdarg
cstddef
cstdint
cstdio
cstdlib
cstring
ctime
cuchar
cwchar
cwctype
initializer_list
limits
numbers
ratio
source_location
type_traits
utility
version

В основном это враперы над хедерами C, но не только.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Части стандартной библиотеки С++ не требуется C++ рантайм.
В основном это враперы над хедерами C, но не только

Да, вся или почти вся STL требует рантайм, потому что утыкана исключениями с ног до головы.

byko3y ★★★★
() автор топика
Ответ на: комментарий от byko3y

Да, вся или почти вся STL требует рантайм, потому что утыкана исключениями с ног до головы.

Я привёл список заголовочных файлов, которые не требуют рантайма.

Вот, например:

https://en.cppreference.com/w/cpp/header/numbers

https://en.cppreference.com/w/cpp/header/type_traits

два хороших заголовочных файла, которые нет смысла реализовывать самому…

fsb4000 ★★★★★
()
Ответ на: комментарий от byko3y

Да, вся или почти вся STL требует рантайм, потому что утыкана исключениями с ног до головы.

Ну да. В чём-то ты прав. Одно дело, когда требуются аллокации. Тогда понятно, и исключения ожидаемы тоже. Но несколько жаль, что string_view и optional, variant требуют рантайма, хотя и не требуют аллокаций :(

Из-за: https://en.cppreference.com/w/cpp/string/basic_string_view/at

и https://en.cppreference.com/w/cpp/utility/optional/value

и https://en.cppreference.com/w/cpp/utility/variant/get

и т.п.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Но несколько жаль, что string_view и optional, variant требуют рантайма, хотя и не требуют аллокаций :(

Так это же шаблоны, просто не вызываем throw’ие методы и вся обвязка для исключений и не нужна. Такое скомпилил без libstdc++

#include <string_view>
using namespace std;

extern "C" void __gxx_personality_v0() {}

int main() {
	const char *c = "djfkdjfk";
	string_view v(c);
	int sz = v.size();
}

$ gcc 1.cc

Вставил заглушку __gxx_personality_v0() - видимо где-то в недрах есть catch блок (но throw нет, иначе нужна __cxa_throw).

Ну а если свои аллокаторы, то можно и vector заюзать. Если памяти не хватает, то можно попробовать сохранять образ процесса через CRUI, ну а там «втыкай RAM» и пробуй снова.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от X512

а ТС затеял как раз маштабный проект, да, все сходится

EugeneBas ★★
()
Ответ на: комментарий от fsb4000

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

EugeneBas ★★
()
Ответ на: комментарий от kvpfs

Небольшое уточнение - в данном случае персональную функцию тянут методы, которые отмечены noexcept. Работает примерно так - где-то бросается исключение, происходит проход по стеку вызовов, для каждого фрейма вызывается персональная функция, которая решает что делать с этим исключением. Звучит странно - нам исключения не нужны вовсе, но unwind интерфейс тянем. В таких слуаях надо собирать с флагом -fno-excptions

gcc 1.cc -fno-exceptions

Естественно, что если заюзаем throw’ий метод, то собрать не удастся из-за отсутсвия необходимых зависимостей. Ну а тот пример со string_view собрался с одной зависимостью от libc.

kvpfs ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.