Как стать автором
Обновить

Комментарии 47

Классы - это скорее всего первое, что добавил Страуструп в далёких 1980х, ознаменовав рождение С++.

Изначально это все называлось C with classes, так что спору нет. Классы в C++ появились в некотором смысле раньше, чем сам C++ :-)

Если представить, что мы археологи древних плюсов, то косвенным подтверждением этого факта для нас будет this, который по прежнему в С++ является указателем, а значит, скорее всего, он был добавлен до "изобретения" ссылок!

Археология тут не нужна, достаточно истории, т.е. работы с источниками :-) Это прямо сказано в "Дизайне и эволюции языка C++". Идея почерпнута из Simula-67, где есть ссылка THIS, но ссылок еще не было в языке, добавлены потом, в первую очередь для поддержки перегрузки операторов, ну и появился указатель this.

Важна не конечная точка, а путь. Да, это всё ещё тот же непрерывный путь, обрастающий новыми деталями и избавляющийся от старых :).

Нет. Это работает с кораблями, Виженом и гномскими топорами, но не с автомобилями.

Так это не автомобиль, а набор деталей. Как соберёшь, так и поедет.

Все языки программирования (со своей эволюцией) нужны только для того, чтобы научить программистов Лиспу. Все там будем :)

Обалдеть. C в своё время был такой лаконичный и понятный язык, а теперь из него сделали какого-то монстра.

Ну, во первых это не С... А что в статье нелаконичное и "монстр"?

Это не C, а его далёкий наследник, да.

А «монстр» – всё. Куча кода, который без статьи совершенно не понятен.

Это хорошо для Perl’а, на котором пишутся «одноразовые» скрипты. Но для продукта, который предполагается развивать и поддерживать, я б такой язык не взял.

Конкретно код в статье по-моему очень даже понятный, если знать синтаксис плюсов

нет, он понятный тем кто над ним работал в течение длительного времени, тогда создается впечатление «очевидности» кода.

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

 если знать синтаксис плюсов

Даже если знать синтаксис плюсов, то остается непонятно зачем это все нужно.

Проблема в том, что когда вы видите foo+bar в сишном коде, вы знаете, что здесь происходит. Но в cpp-коде то же самое может означать что угодно, за этим может скрываться миллион неочевидных операций. Придётся распутывать клубок наследований, перегрузок.

Ну, справедливости ради, в том же Haskell, та же операция foo + bar может означать всё что угодно и даже больше :) Вообще в любом языке, который умеет в перегрузку это может означать всё что угодно)

А если там еще и приоритеты операций менять можно... У-у-у....

На противоположном конце Pharo Smalltalk — 4 приоритета, и не поддерживаются математические: 2 + 2 * 2 даст 8

Ну там же вроде просто операций нету? Я не эксперт и даже им не баловался, только книжку пролистал много лет назад и статью про Design principles почитал, чтобы лучше понимать, откуда у TDD ноги растут, но там вроде это вот все сообщения, которые слева направо и посылаются куда-то там.

если вы видите Bar(Foo()) вы ничего не знаете. Чем такое незнание отличается от A+ B непонятно

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


Но главное, что отследив историю изменений кода (например, git blame), вы знаете, что если эту строчку не меняли с момента написания, то суть происходящего здесь тоже не меняется (вызов двух функций). По крайней мере, даже просто окинув взглядом тело функции, где нет никаких Bar(Foo() или же наоборот есть, вы сходу, за долю секунду получаете представления о том, как выглядит call tree для родительской функции: вызывается ли отсюда что-либо ещё (что требует расмотрения, анализа, оптимизацию), или мы на «низшей ступени».


Но если переместиться в мир C++, то и за A+B может стоять что угодно (вплоть до вызова sleep или показ какого-то UI — это конечно пример чудовищного говнодизайна приложения, но эта возможно), и за Bar(Foo()) может скрываться не только вызов функции, но и порождение экземпляра классов Foo или Bar, если вдруг это классы. А за порождением экземпляров может (и будет) скрываться обращение к куче, причём оператор new может быть перегружен и это может оказаться какая-то специальная, возможно не самая эффективная куча. То есть за тем, что выглядит как простой вызов функции, может скрываться что-то, что в 10 000 раз тяжелее, чем простой вызов функции. К тому же, если у конструктора Foo тип аргумента не Bar, а какой-то другой, возможно сработает неявное приведение типа.


В Си то, что выглядит как утка, является уткой.


Скрытый текст

Я не говорю, что то, что я выше написал — это однозначное зло. В конце-концов, иначе С++ оказался бы в аутсайдерах. Но это и не однозначное добро. По крайней мере, это как со старыми жигулями: если там на приборной панели показывается, что давление масла нормальное и температура охлаждающей жидкости в порядке — значит так оно и есть. А примеры из современной техники, когда, скажем, устройство показывает завышенный заряд батареи, чтобы пользователь думал, что устройство способно долго сохранять заряд. Реальный заряд маппится на отображаемый функцией с как бы выгнутым графиком. Зато потом, когда устройство на издыхании, отображаемый процент заряда начинает быстрыми темпами приближаться к нулю.

за порождением экземпляров может (и будет) скрываться обращение к куче

абсолютно несвязанные вещи, причём тут куча...

Я точно знаю, что за А + Б скрывается вызов оператора + с аргументами А и Б, не более того

абсолютно несвязанные вещи, причём тут куча...

Окей, глупость сморозил: я посчитал, что для формы


Bar(...);

под капотом вызывается operator new, хотя на самом деле экземпляр порождается на стеке и оператор new не вызывается.


Я точно знаю, что за А + Б скрывается вызов оператора + с аргументами А и Б

Нет, вы точно не знаете, что именно там вызывается. Возможно A и Б является числовыми переменными и происходит обычная арифметическая операция, а возможно это экземпляры классов (разных или одинаковых), для которых определён свой оператор сложения, а возможно они экземпляры классов, но для них определён operator int(), и два объекта приводятся к типу int и совершается банальная арифметика, а возможно только один из объектов приводится к типу int, потому что существует оператор сложения, работающий с классом и int в роли операндов.


Просто глядя на место в коде, где находится А+Б, вы не знаете заранее и наверняка, какая комбинация из приведений типов будет задействована и какая будет реализация оператора сложения будет вызвана.


В Си без плюсов вы знаете, что за А+Б стоит сложение с применением целочисленной или FP-арифметики. И (распространяя и обобщая), что если в коде вашей функции нет явных вызовов других функций, то говоря о результирующем машинном коде, там не будет инструкции CALL или заинлайненного кода какой-то другой функции. Зависимости и побочные эффекты очевидны. Но не в С++.

Подождите, а вы никогда не сталкивались с макросами в си?
Определить макросы Foo() и Bar() это как два пальца.
Более того, иногда еще и с именами, совпадающими с именами каких-то функций, просто потому что так захотелось.

Практика показала, что:

- приватное наследование это чрезвычайно редкий зверь, практически не обитающий в реальном коде;

Это не самая лучшая практика показала-то. Появились Java и иже с ней, где все унаследовано от какого-нибудь Object, и с непубличным наследованием автоматически засада. С оглядкой на это стали писать книжки по OOD (в "Паттернах" написано что-то вроде: "наследование убивает инкапсуляцию", с отсылкой к работе Стайерса от 85-го года, которая на самом деле посвящена тому, как такой ситуации избежать, и которую Страуструп явно читал), по этим книжкам учились люди, которые потом стали писать на C++ ... А в самих плюсах акцент сместился на мета-программирование, что само по себе неплохо, но не является поводом избегать непубличного наследования.

C++, как и emacs, разве что кофе не варит

Думаю С++ используется в кофеварках

Хочу научиться прогать на современном стандарте плюсов, но каждый раз получается С с классами.

Есть какая-нибудь статья, сдвигающая мышление на новый лад?

Ну, можно посмотреть сторонние языки по типу Java или C#, где присутствует жёсткая привязка к ООП и опыт оттуда перенимать в плюсы. Ну и, как минимум, перестать использовать всякие чисто сишные функции и структуры данных и пересесть на плюсовые классы (хотя бы тот же STL). Ну и про всякие принципы SOLID не забывать.

Из джавы и С# пожалуйста не надо... Эти языки привязались к парадигме слишком сильно и из-за этого кривые

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

Если решить задачу с применением олдскульных подходов получается быстрее и проще, может это повод задуматься, что не всё так хорошо с новомодными подходами?

Не быстрее и не проще.

вы сами-то в это верите? Или это с позиции человека, который не работал на с++ достаточно, чтобы даже освоить основы с++11?

Ответ на близкую тему:
я в своё время так хотел научиться "ФП", но дело дальше замены foreach на map не шло (что явно карго-культ).

Просто сел и прошёл на https://adventofcode.com/ два эвента (по 25 задачек в эвенте) на хаскеле. Мне помогло. Ряд хороших идей из ФП почерпнул.

Что использовать для "обобъекчивания" - не знаю.

У C++ есть фактически два пути применения (даже три, но метапрограммирование стоит отложить) это работа со стандартной библиотекой, и без неё. По сути развитие C++ - это в большей степени расширение стандартной библиотеки. И изучение новых стандартов нужно подходить именно к изучению возможностей стандартной библиотеки. Нововведения самого языка изучаются за пару вечеров, их не так много, и сводятся к изучению всех новых способов выстрелить в ногу, что эти нововведения привнесли

На канале Cᐩᐩ Weekly With Jason Turner недавно вышло видео (Ep 318) о том, как сделать каррирование в современном C++ и получилось очень даже неплохо.

К сожалению реализация там слабенькая, даже перфект форвардинга нет. В современном С++ есть уже std::bind_front ну и начиная с С++11 есть std::bind, плюс то что в статье вполне нормально каррирует

Я может не внимательно читал, но я не заметил реализации рантайм полиморфизма на структурах.

но она есть

Где?

Там 1090 строк, в которую смотреть?

в примеры и ридми, где описано что динамический полиморфизм там есть и работает

Так как работает-то? Волшебным образом?

ну это уже смотрите в реализацию, там всё довольно понятно написано

Если не знаете - так и скажите. Чего стрелки переводить?

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

Так и чем ваш велосипедный vtable лучше нативного?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации