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

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

Миф 1: запомни это, запомни то

Мы все знаем, что C печально известен ручным управлением памятью, например, с помощью malloc и free. С ними трудно работать, они приводят к множеству ошибок, которые нужно проверять вручную, и в целом являются настоящим кошмаром. Когда люди слышат, что C++ также отличается высокой производительностью, то думают, что это происходит за счет того, что все особенности распределения памяти такие же, как и в C, следовательно, из этого делается вывод, что и здесь всё будет ужасно. Это категорически неверно.

Уже некоторое время в C++ существуют умные указатели. Используя их, можно добиться того же поведения, что и с объектами в других языках, таких как Java и Python. В частности, std::shared_ptr работает, оборачивая обычный объект в копируемый и перемещаемый объект с механизмом подсчета ссылок. Таким образом, когда ни один код не ссылается на shared_ptr, он благополучно ликвидируется и освобождается, как в большинстве языков. Простейший способ создания разделяемого указателя выглядит следующим образом:

std::shared_ptr cat(new Pet("cat"));
// or
std::shared_ptr cat = std::make_shared<Pet>("cat");

Хотя это общий шаблон для большинства других языков, C ++ позволяет вам дополнительно контролировать, как осуществляется доступ к объекту, например, используя unique_ptr, но об этом позже.

В целом, с использованием умных указателей, управлять памятью в C++ не сложнее, чем в других языках. Это сделано преднамеренно, поскольку вам необходимо уточнить, каким будет ожидаемое поведение, потому что вы по-прежнему можете создавать и передавать обычные указатели старым добрым (безобразным?) способом.

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

Миф 2: он старый и неактуальный

C++ очень активно поддерживается, и новые возможности продолжают регулярно появляться. Я думаю, что одной из самых распространенных "новых" функций во многих языках, которыми люди восхищаются, являются лямбды. Конечно, в C++ нет лямбд, верно? Ошибаетесь. Лямбда-функция в C++ может быть определена как:

auto square = [=](int x) { return x * x; };

Для контекста, Java получила лямбды в 2014 году с выходом Java 8. В C++ лямбды появились с C++11 (2011). Вот так.

И продолжают выходить важные обновления, например, C++20 совсем недавно, которые добавляют еще больше возможностей, чтобы упростить работу с C++. Например, аргументы переменной длины уже давно есть в C++ благодаря вариативным шаблонам. Дженерики также отлично работают в C++, хотя и не так, как вы привыкли в Java. Эти возможности продолжают улучшать способы разработки программного обеспечения.

Миф 3: легко ошибиться

Напротив, несмотря на то, что изучение некоторых его особенностей может занять некоторое время, в C++ очень сложно сделать так, чтобы ваш код сделал нежелательные вещи. Например, во многих объектно-ориентированных языках нет поддержки "чистых" функций, то есть функций, которые гарантированно неизменяемы. В C++ вы можете пометить методы класса как const, если они не изменяют его состояние. Затем эти методы также можно вызывать для постоянных экземпляров класса. Вот пример:

class Greeting {
public:
Greeting(std::string greeting) : greeting_(greeting) {}

std::string get_greeting() const { 
  return greeting_; 
}
std::string set_greeting(std::string new_) { 
  greeting_ = new_; 
}
private:
std::string greeting_;
};

Теперь вы можете инициализировать этот класс как константу и по-прежнему вызывать геттер. Когда вы попытаетесь изменить состояние класса, компилятор будет ругаться!

const Greeting g("hello");
g.get_greeting(); // returns "hello"
g.set_greeting("hi"); // does not compile

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

Может показаться, будто я противоречу сам себе, упоминая такой крайний случай в распространенном примере использования. Тем не менее, я не думаю, что это опровергает мое основное утверждение: В C++ трудно ошибиться, если вы знаете, чего хотите, поскольку он дает вам инструменты для выражения в коде именно того, что вам нужно. Хотя такие языки программирования, как Python, могут абстрагировать все это от вас, они обходятся гораздо дороже. Представьте, что вы идете в магазин мороженого, а вам подают только шоколад, потому что большинство людей обычно хотят именно его - это и есть Python. В зависимости от того, как вы на это посмотрите, конечно, в некотором смысле с шоколадом сложнее ошибиться, но в целом не магазин, а сам пользователь должен определять, что ему нужно.

Хорошая const-ность — это большой плюс, но есть еще несколько вещей, которые позволяет делать C++ и которые предотвращают появление багов в продакшене больших проектов. Он позволяет вам настраивать семантику перемещения/копирования/удаления для классов, которые вы разрабатываете, если вам это необходимо. Позволяет передавать данные по значению и использовать такие расширенные возможности, как множественное наследование. Все эти вещи делают C++ менее строгим.

Миф 4: он многословен

Хорошо, здесь есть доля истины. Придя из Python, где люди так устали от ввода numpy, что все коллективно решили импортировать его как np, ввод более 2 букв действительно кажется многословным. Но современный C++ гораздо менее многословен, чем раньше! Например, вывод типов, как в Go, стал доступен в C++ с введением ключевого слова auto.

auto x = 1;
x = 2; // works
x = "abc"; // compilation error

Вы также можете использовать auto в типах возвращаемого значения:

auto func(int x) { return x * x; }

Можно использовать его в циклах, например, для перебора карт:

for (auto& [key, value]: hashmap) {...}

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

Вы также можете указывать псевдонимы типов и/или пространств имен, как в Python. Например, можно сделать что-то вроде:

using tf = tensorflow;
// Now you can use tf::Example instead of tensorflow::Example.

Шаблоны C++ (похожие на Java Generics, но при этом довольно разные) также помогают значительно сократить дублирование кода и могут оказаться элегантным решением для многих случаев использования.

В целом, C++ определенно более многословен, чем большинство новых языков программирования, таких как Kotlin и Python. Однако он не намного больше, чем C#, Java или даже JavaScript, и их многословность не сильно повлияла на популярность этих языков.

Миф 5: трудно делать простые вещи

Здесь снова не все так однозначно. Обычные операции, такие как объединение строк разделителем, сложнее, чем нужно. Однако эта проблема довольно легко решается с помощью библиотек с открытым исходным кодом, таких как Abseil от Google, содержащих тысячи вспомогательных функций, с которыми очень легко работать. Помимо строковых утилит, Abseil также содержит специальные реализации хэшмапов, хелперов параллелизма и инструментов отладки. Другие библиотеки, такие как Boost, облегчают работу с BLAS-функциями (например, точечными продуктами, матричными умножениями и т.д.) и отличаются высокой производительностью.

Использование библиотек само по себе может быть сложной задачей в C++ с необходимостью поддерживать файлы CMake, хотя во многом они мало чем отличаются от файлов gradle или package.json в других языках. Однако, опять же, инструмент сборки Bazel от Google с открытым исходным кодом чрезвычайно упрощает работу даже с кросс-языковыми сборками. К нему нужно привыкнуть, но он обеспечивает действительно быструю сборку и в целом очень удобен для разработчиков.

Сверхвозможности

Итак, развеяв все эти распространенные мифы о C++, вот некоторые вещи в C++, которые многие другие языки не позволяют вам делать:

Настройте семантику ваших классов

Предположим, у вас есть класс, который содержит большие данные. Вы бы предпочли, чтобы они не копировались, а всегда передавались по ссылке. Как разработчик интерфейса вы можете это реализовать. Более того, при необходимости вы можете настроить, как именно будет происходить копирование данных.

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

Уничтожение объектов — при выходе объекта из области видимости, возможно, вы автоматически хотите освободить некоторые ресурсы (например, мьютексы, которые автоматически освобождаются в конце функции). Это работает примерно так же, как функция defer в Go.

Как дизайнер интерфейса, вы можете настроить каждый маленький аспект того, как пользователи будут использовать ваш класс. В большинстве случаев в этом нет необходимости, однако, если возникает такая потребность, C++ позволяет вам полностью выразить свои потребности. Это очень мощный инструмент, который может сэкономить вашей компании многие часы потраченного времени на крупных проектах.

Оптимизируйте процесс доступа к памяти

Выше я вкратце упомянул об умных указателях. Помимо shared_ptr, вы также можете использовать unique_ptr, который гарантирует, что только один объект может обладать ресурсом. Наличие одного владельца данных упрощает организацию и обсуждение крупных проектов. На самом деле, хотя shared_ptr наиболее близко имитирует Java и другие языки OOP, в целом лучше использовать unique_ptr. Это также повышает безопасность языка.

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

Строгая типизация

В целом, я заметил, что работу с типизированными объектами в C++ намного легче отлаживать, чем в других языках.  Например, после нескольких часов отладки проекта на JavaScript я обнаружил, что ошибка возникала из-за того, что я передавал 1 аргумент в функцию с 2 аргументами. При этом JavaScript не выдавал никаких ошибок и просто производил нежелательные результаты. Я бы предпочел, чтобы ошибка возникла при компиляции, а не во время выполнения.

Однако существует множество типизированных языков, поэтому использование этой суперспособности может показаться излишним. Но C++ делает больше. Во-первых, C++ позволяет передавать что-либо по ссылке, по указателю или по значению. Это означает, что вы можете передать ссылку на целое число и попросить функцию изменить его вместо того, чтобы использовать возвращаемое значение (в некоторых случаях это может быть удобно). Это также означает, что при необходимости вы можете выполнять операции в памяти, используя указатель на что угодно. Обычно, однако, вы хотите передавать объекты в виде постоянной ссылки (например, const A&), что не приведет к копированию и защитит ваш объект от непреднамеренного мутирования. Это сильные гарантии, и благодаря им ваш код становится намного проще для понимания. В TypeScript, например, вы не можете переназначить const-объект, но можете мутировать его. 

Предостережения

Итак, C++ - это здорово и все такое, но есть очевидные ограничения относительно его использования. На нем можно легко писать микросервисы (например, на gRPC), но я бы, скорее всего, не стал использовать C++ для реального веб-сервера (для этого лучше применить TypeScript). Несмотря на его скорость, я бы не стал использовать C++ для анализа данных (для этого я бы, скорее всего, выбрал pandas). Есть некоторые вещи, для которых C++ подходит отлично, а для других он просто не годится. В конечном счете, вы все равно должны выбрать правильный инструмент для той работы, которую собираетесь выполнить. Надеюсь, эта статья сделала C++ немного более привлекательным в ваших глазах, чем вы привыкли его видеть.


Материал подготовлен в рамках курса «C++ Developer. Basic».

Комментарии (104)


  1. BeardedBeaver
    02.09.2021 13:52
    +28

    Другие библиотеки, такие как Boost, облегчают работу с BLAS-функциями (например, точечными продуктами

    Серьезно?


    1. symbix
      02.09.2021 14:05
      +10

      Тот редкий случай, когда google translate справился бы лучше.


    1. AllexIn
      02.09.2021 21:10
      +5

      Я пошел в комменты чтобы найти этот комментарий.


      1. mapron
        03.09.2021 02:12
        +4

        Гуртовщик мыши, щелчок по почкам!


  1. GamePad64
    02.09.2021 14:21
    +2

    C++ -- ужасный язык. Проблемы в статье высосаны из пальца. А вот его реальные проблемы:

    1. Нет стандартной структуры проекта. Из-за этого каждая система сборки навязывает свой подход, слабо совместимый с остальными тулзами. Либо не навязывает и кодовая база превращается в хаос.

    2. Нет единого формата пакета. Из-за этого бардак с пакетными менеджерами. Есть conan, гвоздями прибитый к python со своими тараканами. Есть vcpkg, который работает далеко не везде и не со всеми библиотеками и выглядит сырым. И так далее. Отсутствие пакетного менеджера в 2021 -- это само по себе неудобно.

    3. Тяжёлое наследие Си -- #include, макросы, разделение на хедеры и сорцы (пофикшено частично).

    Вот тут чуть более подробно рассказываю

    Имхо, Комитету нужно брать пример с развития Раста, создатели которого заранее подумали об экосистеме и сделали её удобной.


    1. MentalBlood
      02.09.2021 14:38
      +17

      брать пример с развития Раста, создатели которого заранее подумали об экосистеме и сделали её удобной

      Ну таки Раст относительно новый язык, вот и сделали сходу по-современному


    1. Starl1ght
      02.09.2021 18:23
      +8

      Отсутствие пакетного менеджера в 2021 -- это само по себе неудобно.

      То то мы уже год как переехали на тот самый сырой и... отсутствующий(?) vcpkg и счастливы.

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


      1. Alexey2005
        02.09.2021 20:07
        +22

        Покажите мне хоть один мало-мальски крупный C++ проект на github, где можно было бы просто сделать git clone, а потом собрать одной командой.
        Никогда и ничего не соберётся с первого раза. Да, даже если дословно выполнить те 10 страниц инструкции по сборке, которые там в readme.
        Как-то в XXI веке хочется, чтобы такая инструкция была записана как минимум в виде стабильно работающего скрипта (а лучше вообще декларативно), а все зависимости подтянулись автоматически прямо в процессе сборки.


        1. Starl1ght
          02.09.2021 20:37

          Покажите мне хоть один мало-мальски крупный C++ проект на github, где можно было бы просто сделать git clone, а потом собрать одной командой.

          Собственно, именно это я у себя в проекте на работе и сделал. Билд 1 кнопкой с автоподтягиванием и автообновлением\пересборкой зависимостей и проекта при изменениях. Знаете, сколько это заняло строчек кода?

          В районе 15 строк на повершелле, и 10 строк на MSBuild. Почему люди с гитхаба не хотят\могут это сделать - вопросы к ним.


          1. 0xd34df00d
            02.09.2021 20:47
            +8

            % uname -s
            Linux

            Какие-какие у вас там инструкции для сборки?


            1. Playa
              02.09.2021 21:46
              +3

              cmake --build . --config Release

              Или по кнопке в Visual Studio.

              Под капотом подтянется vcpkg (git submodule), который соберёт все 100+ зависимостей.


              1. 0xd34df00d
                02.09.2021 22:06
                +7

                Тянуть с собой сам пакетный менеджер — это любопытно.


                Кстати, интересно, компилятор он тоже собирать будет, или бинарники притащит, или будет пользоваться системным?


                1. Xadok
                  02.09.2021 22:35
                  +1

                  Бинарники vcpkg не тащит, такое умеет conan. Пакетные менеджеры будут использовать тот компилятор, который вы им укажете.


                1. Playa
                  03.09.2021 00:35

                  Как уже заметили выше, toolchain можно подсунуть свой. Бинарники после сборки кешируются.
                  Как я понял, по задумке Microsoft vcpkg будет встроен прямо в Visual Studio, а пользователи будут пользоваться vcpkg registries, но пока это только планы.
                  PS: с Linux всё немного интереснее из-за того, что библиотеки в папке с исполняемым файлом имеют приоритет ниже, чем системные, но это удалось побороть, подсунув $ORIGIN в rpath.


        1. Kotofay
          02.09.2021 21:33
          -1

          Есть такой проект: OBS.


        1. AllexIn
          02.09.2021 22:51
          +3

          Unreal Engine 4/5

          Собирается с первого раза, без плясок с бубном


        1. myrrc
          03.09.2021 01:07
          +4

          Если не-кросплатформенная сборка подходит (а точнее, amd64 Linux), то Clickhouse. Достаточно склонировать репозиторий со стабильной версии, проинициализировать субмодули и собрать одной командой.


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


          Правда, еще требуется система сборки (ninja) и генератор для этой системы (cmake), но без этих зависимостей действительно вряд ли получится собрать любой более-менее сложный проект.


        1. mrbaranovskyi
          03.09.2021 09:48
          +1

          Ну да... обычно, при переходе в другую команду, со старым плюсовым проектом, первое задание, зачастую на несколько дней - скомпилить проджект. Часто это заканчивалось тем, что сидели и компилили всей командой. "Главное один раз сбилдить, а потом главное не удаляй статические либы.. ибо заново". Еще когда-то был случай когда в процессе "разработки" каким-то хреном добавили кросс-зависимость между двумя библиотеками. И получилось, что А не билдится без Б, а Б не билдится без А... В том же .net, такой проблемы не может быть по определению..


        1. ZhilkinSerg
          03.09.2021 20:22

          Никогда и ничего не соберётся с первого раза.

          Но это же очевидная ложь.


      1. northzen
        03.09.2021 13:36
        +3

        Да-да, vcpkg такой классный, что не давал выбрать версию пакета год назад:

        https://github.com/microsoft/vcpkg/issues/1681

        А сейчас даёт? Как может быть менеджер пакета без возможности установки точной версии, когда речь идёт о сложном проекте?


    1. domix32
      03.09.2021 11:43

      На самом деле часть пропозалов были вдохновлены растом. Там и паттерн матчинг в планах и развитие метапрограммирование, и те же модули и даже про лайфтайм переменных чото было. Только реализаций даже 21 стандарта толком нет пока.


    1. VioletGiraffe
      04.09.2021 17:54

      1. Не понял суть претензии. То есть я верю, что для вас это проблема, но вот я уже больше 10 лет профессионально программирую на С++, и не только не испытывал такой проблемы, но даже плохо понимаю, в чем она заключается.
      3. Разделение на хедеры и сорцы — это гениально. .cpp и хедер — это как книга и её оглавление. Очень удобно и полезно.


      1. AnthonyMikh
        06.09.2021 16:50

        3. Разделение на хедеры и сорцы — это гениально. .cpp и хедер — это как книга и её оглавление. Очень удобно и полезно.

        А чем это хорошо? Из-за шаблонов мифическое ускорение компиляции за счёт параллелизма несколько нивелируется, а редактировать объявление и реализацию синхронно — неудобно и способствует внесению ошибок.


        1. VioletGiraffe
          06.09.2021 16:57

          Именно тем же удобно, чем удобно иметь оглавление к книге. Быстрая навигация по коду, лекго окинуть взглядом весь класс. А ошибки несоответствия объявления и определения компилятор легко и сразу выявляет, анализатор реального времени в IDE тоже.


          1. AnthonyMikh
            06.09.2021 17:00
            +1

            Быстрая навигация по коду

            Это ортогонально делению.


            лекго окинуть взглядом весь класс.

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


  1. MentalBlood
    02.09.2021 14:33
    +2

    люди так устали от ввода numpy, что все коллективно решили импортировать его как np

    Дело не только и не столько во вводе, сколько в чтении. Особенно если numpy встречается несколько раз в одной строке


  1. lamerok
    02.09.2021 16:26
    +5

    Миф 4 - плохой пример

        auto x = 1;
        x = 4294967294; // чему тут x равно теперь?
    
        return 0;

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

    По теме, С++ замечательный язык и особенно метапрограммирование. Возможности его практически не ограничены, но куда развиваться еще есть.


    1. 0xd34df00d
      02.09.2021 20:44
      +14

      и особенно метапрограммирование

      Оно прикольное, если нравится решать ребусы. В личное время на личных проектах — да, с радостью, и раньше я с ним игрался регулярно (и игрался бы и сейчас, если бы не перешёл на более интересные языки). Но делать что-то серьёзное на этом в продакшене — идея так себе. Людей, которые могут разобраться в шаблонной наркомании, не так много, компилируется это всё адово долго, и так далее.


    1. Psionic
      03.09.2021 01:11

      Я вообще против выведения типов - такой код плохо читается.


      1. 0xd34df00d
        03.09.2021 02:13
        +7

        Спорный вопрос. ИМХО почти всегда конкретный тип неважен (особенно если это тонкости хэшера для std::unordered_map) или вообще неназываем (если это лямбда). Да и по опыту работы с языками, где выводом типов пользуются весьма активно, это вполне удобно и читаемо, и достаточно ручного указания типов для топ-левел-функций, и всё.


        1. Psionic
          03.09.2021 10:33
          -1

          Лямбда не называема? Разве для лямбд не работают те же принципы именования что и указателей на функцию? Или я что-то не понял?
          Ну мне например, легче читать код где явно указан тип. Потому что вот у вас есть авто и инициализируеться через функцию, которая неясно что возвращает, неясно откуда импортируется и что делает? Как понять что происходит в коде? Надо ради понимания 40 строк кода, надо еще 4000 строк внешних зависимостей читать.


          1. 0xd34df00d
            03.09.2021 10:38
            +2

            Лямбда не называема? Разве для лямбд не работают те же принципы именования что и указателей на функцию? Или я что-то не понял?

            Ну ок, вот вам лямбда:


            int x = 42;
            auto lam = [&x] { return x; }; // тута

            Что написать вместо auto?


            Потому что вот у вас есть авто и инициализируеться через функцию, которая неясно что возвращает, неясно откуда импортируется и что делает? Как понять что происходит в коде?

            Если у меня написано


            const auto& children = GetAllChildren();
            for (const auto& child : children)
                HandleChild(child);

            то зачем мне тут в теле типы? Что вам скажет, является ли children std::vector<Child> или std::deque<Child> или google_nih_std::vector<Child>?


            1. segment
              03.09.2021 11:45
              -1

              Если я читаю код не в VS, а, например, текстовом редакторе, то мне важно видеть тип данных. Откуда я знаю, что это тип Child? Или может там обертка какая-то используется? В противном случае необходимо смотреть кто что выдает и что куда принимает, читать код с auto очень тяжело. В чем проблема прописывать тип данных?


              1. Free_ze
                03.09.2021 13:05
                +4

                мне важно видеть тип данных

                Зачем? Что даст эта информация при чтении примера?
                Эта необходимость появится при изучении кишков HandleChild, где в объявлении будет все необходимое в явном виде.


                1. Psionic
                  03.09.2021 14:50

                  Затем. Вот вы второй день на проекте - что, чего и как тут в исходниках творится? И у вас уйма лишних телодвижений - на 58 раз это уже люто бесит.


                  1. Free_ze
                    03.09.2021 15:51
                    +2

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

                    Как вы с полиморфным кодом справляетесь?


                    1. Psionic
                      03.09.2021 16:44

                      А это не случайное место, в этом модуле где-то баг, но чтобы понять что произошло, надо как раз разобрать этот модуль, а так как вы второй день на проекте, а UML диаграммы классов вам никто не дал (ее нет), то всех абстракций вы не выучили или еще даже не видели. Да и вообще - вас мог в этот модуль дебагер забуцнуть ошибкой рантайма - будете все читать, прежде чем понимать контекст?


                      1. Free_ze
                        03.09.2021 16:48
                        +2

                        вас мог в этот модуль дебагер забуцнуть ошибкой рантайма
                        Проблема почти наверняка будет выше.

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

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

                        Как вы с полиморфным кодом справляетесь?


                    1. svr_91
                      03.09.2021 16:58

                      Как вы с полиморфным кодом справляетесь?

                      Полиморфный код не сильно лучше обычного. Если можно писать не полиморфно, то лучше писать не полиморфно.

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


                      1. Free_ze
                        03.09.2021 17:10
                        +2

                        Если можно писать не полиморфно, то лучше писать не полиморфно.

                        Наоборот же) Не полиморфно можно писать всегда, но для копипасты должно быть какое-то здравое обоснование.


                      1. svr_91
                        03.09.2021 17:14

                        Вполне можно обойтись без копипасты и без полиморфизма.

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


                      1. Free_ze
                        03.09.2021 17:27
                        +3

                        Статический полиморфизм: шаблоны, макросы, алгоритмы на void* — чуть более, чем везде. С динамическим их роднит главное: оказавшись внутри в отрыве от контекста нельзя точно узнать конкретные типы.


                  1. 0xd34df00d
                    03.09.2021 18:19
                    +5

                    Если вы второй день на проекте, то типы вам помогут только в том случае, если они «словарные» — ну, всё, что в std::. Будет там написано какое-нибудь const IIterable<Child>& = GetAllChildren(); — и что вам это скажет нового?


                1. segment
                  03.09.2021 15:01

                  Может быть, если код приведен в качестве абстрактного примера, то да. В реальных же проектах мне иногда приходится смотреть код без среды разработки или в каком-нибудь diff. И чтобы посмотреть тип приходится лезть в другие файлы, искать определения и возвращаться. Да даже в среде разработки, где можно подсветить выведенный тип, то это все-равно добавляет действий. Намного проще всегда строго указывать тип, большинство типов можно прописать как раз автоподстановкой среды разработки.


                  1. Free_ze
                    03.09.2021 15:38
                    +3

                    Дело не в подсветке IDE. Чтобы понять смысл этого отрывка, знать типы совершенно не обязательно, выразительных средств языка и самоописывающих идентификаторов достаточно.


                1. lamerok
                  03.09.2021 21:34

                  Потому что HandleChild может нечаянно неявно преобразовать тип, который вообще не ожидает , к типу который ему подходит.

                  Это как auto arr = {1,2,3}

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


                  1. 0xd34df00d
                    03.09.2021 21:53
                    +2

                    Преобразование типа может быть и в случае с явным указанием типов. Их надо явно запрещать.


                    Кстати, компиляторы конкретно initializer_list неплохо оптимизируют: тыц. Сравните адовый дизасм для foo1 и foo2, который, однако, после подстановки в соответствующие bar сворачивается в одно и то же.


                    1. lamerok
                      03.09.2021 22:01

                      Да, преобразования типов зло и лучше не делать. Но куча библиотек и Легаси кода , против этой замечательной практики.

                      Согласен, при оптимизации все выглядит неплохо.


            1. svr_91
              03.09.2021 11:49

              Мне тоже кажется, что с типами лучше, чем с auto. Другое дело, что само имя контейнера как правило действительно не нужно. А вот лежащий в его основе тип еще как нужен. Было бы можно писать auto<Child>, было бы круто.

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

              В лямбде как раз тип особо не нужен, потому что лямбда тутже и определяется. Проще посмотреть на заголовок лямбды, чем пытаться вычислить тип. А когда эта лямбда передается куда-то, то уже стоит использовать std::function (хотя она и может нести в себе лишний оверхед над производительностью)


              1. 0xd34df00d
                03.09.2021 18:23
                +3

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

                Такой код и с типами тяжело читать.


                Мой поинт, если хотите, в том, что функции надо писать так, чтобы локальные типы были не нужны для человека.


                В лямбде как раз тип особо не нужен, потому что лямбда тутже и определяется. Проще посмотреть на заголовок лямбды, чем пытаться вычислить тип.

                Вы так, во-первых, не вычислите тип возвращаемого значения, а, во-вторых, в C++14 завезли generic lambdas, так что auto теперь можно встретить и там :]


                А когда эта лямбда передается куда-то, то уже стоит использовать std::function

                Где, в точке определения или использования?


                (хотя она и может нести в себе лишний оверхед над производительностью)

                Он там почти гарантированно будет.


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

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


                1. svr_91
                  03.09.2021 20:49

                  Мой поинт, если хотите, в том, что функции надо писать так, чтобы локальные типы были не нужны для человека.

                  Вы привели слишком простой пример. В реальности это может быть вектор из Child, вектор из Child*, вектор из smart_pointer<Child>, хуже того, это может быть вектор из пары <индекс в бд, Child>...

                  Вы так, во-первых, не вычислите тип возвращаемого значения

                  В заголовке лямбды его можно тоже указать

                  Где, в точке определения или использования?

                  В сигнатуре той функции, что принимает лямбду


                  1. 0xd34df00d
                    03.09.2021 21:55
                    +4

                    Вы привели слишком простой пример. В реальности это может быть вектор из Child, вектор из Child*, вектор из smart_pointer<Child>, хуже того, это может быть вектор из пары <индекс в бд, Child>...

                    Пока тип элемента, лежащего в контейнере, совпадает с типом, принимаемым HandleChild, вам совершенно всё равно на эту конкретику.


                    В заголовке лямбды его можно тоже указать

                    Да. Но я, мягко скажем, давно (даже до выхода C++14, где разрешили его не указывать для многострочных лямбд, компиляторы стали поддерживать это без ворнинга об экстеншоне сильно раньше) не видел кода, который бы это делал.


                    В сигнатуре той функции, что принимает лямбду

                    А зачем в контексте данной дискуссии? В точке вызова вы эту сигнатуру всё равно не видите.


                    1. svr_91
                      03.09.2021 22:35
                      -1

                      Пока тип элемента, лежащего в контейнере, совпадает с типом,

                      Ну у вас какойто уж очень ограниченный пример получился

                      А зачем в контексте данной дискуссии?

                      Потомучто можно вместо function темплейт тотже указать например. Или auto. Но зачем?


                      1. 0xd34df00d
                        03.09.2021 22:42
                        +2

                        Ну у вас какойто уж очень ограниченный пример получился

                        Достаточно репрезентативный, ИМХО, для среднего хорошо написанного тырпрайзного (inb4 оксюморон) кода.


                        Ну или сменится у вас тип с Child на Child* — ну ругнётся компилятор, ну замените точку на стрелочку или добавите звёздочку. Зачем явно указывать типы, когда с этим справится компилятор?


                        Потомучто можно вместо function темплейт тотже указать например. Или auto. Но зачем?

                        Чтобы избежать оверхеда от std::function, например, пока function_ref не завезли.


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


                      1. svr_91
                        03.09.2021 22:53
                        -1

                        ну ругнётся компилятор

                        Компилятор ругнется через 40 минут после нажатия на кнопку build. А результат мне нужен сейчас.

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

                        Это по крайней мере на один хоп ближе к нахождению настоящего типа. К томуже в месте создания переменной ее вид также виден


                      1. 0xd34df00d
                        04.09.2021 03:15
                        +3

                        Компилятор ругнется через 40 минут после нажатия на кнопку build. А результат мне нужен сейчас.

                        Что у вас там за кодовая база? Даже в совсем адовых и полных шаблонов TU время компиляции у меня не превышало десятка минут (для одного файла). В подавляющем большинстве случаев для вашего среднего тырпрайз-кода — десяток секунд до ошибки максимум, а подчёркивание красненьким с условным clangd — почти моментальное.


                      1. alsoijw
                        12.09.2021 14:08

                        Хромуим с нуля может больше суток собираться, так что это не на столько не реалистично.


                      1. Free_ze
                        12.09.2021 17:04

                        С нуля будут собирать только заведомо рабочий код.


                      1. alsoijw
                        12.09.2021 14:09

                        Компилятор ругнется через 40 минут
                        Вы пытаетесь уйти от одной проблемы и тут же натыкаетесь на другую.


            1. Psionic
              03.09.2021 12:40
              -1

              <source lang="cpp"> std::function<int(void)> lam = [&x] { return x;}; // для списка захвата

              int (*lam)() = { return 20; }; // без списка захвата </source>

              то зачем мне тут в теле типы? Что вам скажет, является ли children

              Что за тип child? Это самостоятельный тип? Это интерфейс, за которым пачка классов, типа новорожденый, детсадовец, подросток? Я его впервые вижу вот - жамп ту дифинишен как выполнить?


              1. 0xd34df00d
                03.09.2021 18:46
                +2

                std::function

                Нет, это не тип лямбды. std::function можно сконструировать из лямбды, но они не тождественны. Компилятор из них даже сгенерирует сильно разный код.


                Тип лямдбы настолько неназываем, что две синтаксически одинаковые лямбды имеют разный тип:


                auto l1 = [x] { return x; };
                auto l2 = [x] { return x; };
                std::is_same_v<decltype (l1), decltype (l2)>;

                на последней строке у вас будет false.


                С указателями на функцию та же фигня, кстати — лямбды без capture list тоже только приводятся к указателям на функции, но не являются ими.


                Что за тип child? Это самостоятельный тип? Это интерфейс, за которым пачка классов, типа новорожденый, детсадовец, подросток? Я его впервые вижу вот — жамп ту дифинишен как выполнить?

                Если вы его впервые видите, то разницы между Child, decltype(child) и так далее для вас нет никакой — вы точно так же не знаете, является ли Child интерфейсом, тайпдефом или ещё чем.


            1. svr_91
              03.09.2021 17:31

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


            1. lamerok
              03.09.2021 21:28
              -1

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

              Таким образом, вам нужно будет эту универсальность вести до самого конца. Но так редко бывает, где- то в самом низу обычно все кастится к какому нить указателю на uint8_t и по байтам передаётся в наружу.

              В общем для встроенного софта иногда из за этого бывает больно.


        1. lamerok
          03.09.2021 21:37

          Это хорошо, когда весь код написан, следуя принципам формальных языков программирования, когда от самого низа, до верха все типы выводятся и проверяются. И вообще весь код доказывается.

          Но в реальном софте, особенно встроенном, где половина на Си писана, так не выйдет и где то придётся делать явное преобразование одного типа в другой, и хорошо бы в этот момент знать, какой тип ты преобразуешь.


          1. 0xd34df00d
            03.09.2021 21:56
            +1

            Не, понятно, что для любого best practice'а (включая core guidelines) найдутся исключения, когда это работать не будет. Но мы ж обсуждали код в среднем, и auto как фичу в общем.


            1. lamerok
              03.09.2021 22:03

              Ну сама фича то, огонь. Особенно когда длинующий шаблонный тип выводит, так вообще.

              Просто, пользовать надо с умом, хотя это ко всему относится.


    1. pdragon
      03.09.2021 12:57

      Какого типа будет x в данном примере? Я так понял auto это аналог dynamic из c#?


      1. TargetSan
        03.09.2021 12:59
        +2

        Нет, аналог var.


      1. lamerok
        03.09.2021 21:53

        тип будет int, как ранее уже сказали, это аналог var.


        1. pdragon
          04.09.2021 17:42

          Понял, я написал раньше чем там писали потому не видел.


  1. regent
    02.09.2021 17:37
    +11

    Автор оиригинала так и не узнает что функция get_greeting() в Мифе 3 не является чистой..


  1. iboltaev
    02.09.2021 19:03
    +23

    в C++ очень сложно сделать так, чтобы ваш код сделал нежелательные вещи

    да ладно??

    Автор (оригинала) видимо до STL еще не добрался, не удалял текущий итератор, не разыменовывал битый итератор, не забывал проверить итератор на end, не натыкался на виртуальные деструкторы, не забывал копирущий конструктор/оператор, не инициализировал члены класса не в том порядке, не забывал один из shared_ptr-ов сделать weak_ptr-ом, не передавал случайно здоровенный vector по значению, не забывал виртуально отнаследоваться, когда внезапно оба родителя имеют общего предка ...

    Хотя это может просто у меня в свое время руки кривоваты были, не знаю.


    1. Starl1ght
      02.09.2021 19:48
      +2

      бОльшая часть того, что вы написали - детектится Решарпером или clang-tidy.


      1. 0xd34df00d
        02.09.2021 20:48
        +9

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


      1. Xadok
        02.09.2021 22:37
        +2

        Всё это появилось совсем недавно, а в некоторых случаях даже настроить это совсем не просто. Но всё еще множество вещей в compile-time не детектится.


  1. dkfrmmnt
    02.09.2021 19:55

    Мне кажется, статья будет честнее, если все "можно" заменить на "вам придется", и оставить на откуп читателям самим решать, хорошо это или нет


  1. tenzink
    02.09.2021 21:00
    +4

    Касательно мифа 1, мне кажется, что ценность shared_ptr для C++ сильно преувеличена. Сколько я ни сталкивался с использованием std::shared_ptr в нашей кодовой базе, его можно было (и стоило) заменить на std::unique_ptr


    1. Akon32
      02.09.2021 22:04
      +1

      Неужели в вашем проекте на объекты нет более одной ссылки? В мало-мальских сложных проектах shared_ptr полезен.


      1. tenzink
        02.09.2021 23:32
        +5

        shared_ptr это не про ссылки, а про управление временем жизни. Чаще всего при работе с объектом известно, что он будет существовать всё время вызова функции и не нужно забирать владение - в таком случае передача по ссылке работает прекрасно. Если нужно создать и вернуть объект единственному владельцу, то отлично работает unique_ptr . Для меня редкий сценарий, когда время жизни объекта неясно, нет одного объекта, определяющего время его жизни. Тогда да, shared_ptr уместен. Однако в моём случае такая потребность почти не возникает

        .


        1. mapron
          03.09.2021 02:16
          +1

          Согласен с вами полностью. shared_ptr крайне полезен сам по себе, но очень «overused», «чрезмерно употреблен». Я вот вижу shared_ptr в коде (а у нас в проекте их все еще предостаточно) — сразу думаю «что-то тут не продумали с лайфтаймом/владением».


    1. iboltaev
      03.09.2021 08:57
      +2

      это правда, Страуструп по-моему тоже об этом писал (ссылку лень искать). Продуманное владение почти вытесняет shared_ptr, и более того, без продуманного владения shared_ptr не спасет, будут либо циклические ссылки все равно, либо weak_ptr-ы пустые тогда, когда объект еще нужен, и так далее. Однако shared_ptr нужен, если у вас многопоточка, и потоки друг другом не владеют и друг друга не ждут.


  1. opetrenko
    02.09.2021 21:16
    +3

    давно по этой статье выучил немного разобрался с С++
    How to Program in C++ (fit.edu)

    вот бы где так же сжато, без воды но учетом всего что добавлось за 20 лет.


  1. Alexey2005
    02.09.2021 21:47
    +8

    Придя из Python, где люди так устали от ввода numpy, что все коллективно решили импортировать его как np
    О, от того, как сейчас используют Python, у меня пригорает так, что я не просто улетаю в космос на собственной реактивной тяге, но ещё и врубаю гипердвигатель, переходя в гипербаттхертпространство.
    Знаете, когда-то я думал, что в IT-природе не может существовать ничего худшего, чем PHP-говнокод, создаваемый провинциальными веб-студиями. Но потом в этот мир пришли Data Science и Machine Learning и пробили дно так, что снизу уже и стучать неоткуда.
    Откройте любой Github-реп по ML (даже от крупных контор), где грузится и тренируется какая-нибудь нейросеть — и там под капотом будет просто лютейшая жесть, собранная из говна и палок, подпёртая костылями и перемотанная синей изолентой. Это просто рафинированный, эталонный говнокод, на который даже дышать страшно, чтоб там всё не посыпалось.
    Тесты? Не, не слышали. Документация? В словаре нашего токенизатора нет такого слова. Системные требования? Ну, самая популярная тема для Issues в мире ML — «а сколько минимально надо VRAM, чтоб это запустить», потому что никто и никогда даже не скажет вам, какие нужно выделять ресурсы на запуск этих поделок.
    Реально, всё ML производит впечатление студенческих наколеночных поделок. Причём созданных теми студентами, которые выучили Python две недели назад и не способны даже сделать pip freeze, чтобы сформировать нормальный requirements.txt. В каждом проекте хотя бы пару-тройку зависимостей туда добавить забудут, причём не тех, которые подтягиваются pip со стандартных репов, а кастомных, которые они с чужих github-репов накачали.


    1. Akon32
      02.09.2021 22:35
      +7

      ML и разработка ПО - разные в реализации вещи. ML больше напоминает "научный подход" (т.е. принятый в научной среде) - "максимально быстро пробуем over 9000 вариантов, ненужные 8997 вариантов выбрасываем" (да, код написанный учёными - обычно такой). Разработка ПО, если это серьёзный проект, требует архитектуры, code style, комментов... Но если вы пытаетесь создать совершенно новый алгоритм для решения нерешённой людьми задачи - вероятно, вы скатитесь в перебор десятков-сотен вариантов алгоритма, и тут уже выгоднее быстро написанный говнокод.

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


    1. yokotoka
      04.09.2021 14:11

      Не каждому коду суждено жить долго. Часто он нужен "здесь и сейчас", чтобы завтра его выбросить на помойку (github, ага). Сейчас большая часть ML - это склеивание готовых компонентов на изоленту и матершину, чтобы сдать курсач или получить премию за прогрессивный и модный "мэшн лёнинг" в проекте. Для ML-моделей, которые в принципе "хз как работают, но, вроде, работают" очень гармонично подходит такого же качества код.

      Согласитесь, "студентами, которые выучили Python две недели назад" - звучит как неслабая такая реклама Python? :)


  1. Akon32
    02.09.2021 22:11
    +2

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

    Пока в проекте не появятся забористые циклические ссылки, которые не представляют проблем для gc и даже могут упрощать код (в таком случае есть просто ссылка на объект, нет вариантов из */&/weak_ptr/unique_ptr/shared_ptr/etc, нет конструкторов копирования/перемещения), но в языках без gc называются плохой архитектурой.


    1. reishi
      03.09.2021 15:44

      Упрощение кода как побочный результат подтирания задницы разработчику плохой архитектуры

      В языках с gc также успешно все течет при кривых руках


    1. AnthonyMikh
      03.09.2021 15:58

      На мой взгляд, если у вас циклические ссылки гетерогенные, то они и в языке с GC будут плохой архитектурой.


      1. Akon32
        03.09.2021 17:05
        +1

        Что вы имеете в виду под "гетерогенные ссылки"?


        1. AnthonyMikh
          06.09.2021 16:58

          Когда ссылки образуют цикл, объединяющий значения разных типов


  1. dimaaannn
    03.09.2021 00:48
    +2

    Плюсы весьма сложны для входа, а оплачиваются даже меньше чем более распространённые и простые языки.

    Выбор прост - либо 2-3 года изучать всё что там накрутили за столько лет, либо за год изучить какую нибудь жаву или шарпы.


  1. funny_falcon
    03.09.2021 09:02

    У меня две претензии к языку:

    • Сохранение "совместимости с C" в плане преобразования типов: сделать шаблон, надёжно различающий int, bool и поинтер - задача не тривиальная.

    • Передача "по ссылке" ни как не выделяется на стороне вызывающего. Если "ссылки" задумывались как "не NULL" поинтеры, то лучше было сохранить взятие адреса при передаче параметра.


    1. 0xd34df00d
      03.09.2021 09:27
      +2

      Передача "по ссылке" ни как не выделяется на стороне вызывающего.

      Зачем её выделять?


      Если "ссылки" задумывались как "не NULL" поинтеры, то лучше было сохранить взятие адреса при передаче параметра.

      Ссылки задумывались, чтобы можно было написать a + b независимо от того, являются ли эти термы интами, комплексными числами или вообще векторами в N-мерном пространстве с указателем на аллоцируемую в рантайме память под капотом. Прикручивать сюда & (а на самом деле другую закорючку, иначе будут неоднозначности) — как-то выглядит уродливо.


      1. funny_falcon
        03.09.2021 10:31
        +2

        > Передача "по ссылке" ни как не выделяется на стороне вызывающего.

        Зачем её выделять?

        Затем, что когда читаешь код, хочется знать, что происходит: может у тебя под ногами переменная поменяться, или всё-таки нет.

        > Если "ссылки" задумывались как "не NULL" поинтеры, то лучше было сохранить взятие адреса при передаче параметра.

        Ссылки задумывались, чтобы можно было написать a + b независимо от того, являются ли эти термы интами, комплексными числами или вообще векторами в N-мерном пространстве с указателем на аллоцируемую в рантайме память под капотом.

        В данном примере явно прослеживается применение const ссылок. Ок, согласен, что передачу const ссылок можно было бы сделать без взятия адреса, а передачу мутабельных - с взятием. Меня бы такое устроило.


        1. 0xd34df00d
          03.09.2021 10:39
          +1

          Так в С та же проблема, к слову — синтаксической разницы между получением из T указателей const T* и T* нет.


          Впрочем, лично я мутабельность вообще не люблю, и основания для вашего недовольства разделяю.


        1. Playa
          03.09.2021 11:39

          Решарпер умеет такое подсказывать уже где-то год.


          1. funny_falcon
            03.09.2021 14:06

            А причём тут решарпер и С++ ? Вы имели в виду Clion?

            А когда diff просматриваете в GitHub/GitLab, вам решарпер поможет?


            1. BeardedBeaver
              03.09.2021 15:03

              Решарпер и в плюсы умеет весьма неплохо. Только по моему опыту он достаточно сильно тормозит на больших проектах при выполнении некоторых задач.



      1. Ritan
        03.09.2021 12:35
        +3

        Давно и прочно пишу на C++, но реально нравится явное использование ссылок на стороне вызывающего кода в rust. Иначе иногда непонятно может ли что-то поменяться или нет. А const не воспользоваться т.к с ним move-семантика не уживается


        1. svr_91
          03.09.2021 12:45
          +2

          Не совсем. Если переменная передана как ссылка, то ссылку брать не требуется. Например, в таком примере

          fn g2(i: &mut u8) {}
          
          fn app(i: &mut u8) {
              let mut j = 5;
              g2(&mut j);
              g2(i);
          }

          Для переменной j ссылка нужна, для i уже нет.

          Это конечно логично, потому что i это уже сама по себе ссылка, но просто читать код в надежде выцепить из него все &mut уже недостаточно, в результате приходится вглядываться в код также пристально, как и в c++


          1. AnthonyMikh
            03.09.2021 16:00

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


  1. yokotoka
    04.09.2021 14:06
    +1

    В новом стандарте сломан не сам C++. Его-то, как раз, более-менее починили.

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


  1. DeepFakescovery
    06.09.2021 17:54
    -2

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