Pull to refresh

Comments 160

Моя целевая аудитория здесь — люди, которые переходят со старого (т.е. 98/03) С++ на современный (т.е. 2011 и далее) С++.

Прошу, конечно, прощения, но старый - это C++11, а C++98 - это просто древность

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

У нас в проекте техническая возможность перейти на что-то новее C++98 вот только к концу этого года ожидается.

Это-то понятно, но специалист обычно следит за развитием своего языка, а соответственно, C++11 новым для него не является -- даже если по работе он вообще пишет на чистом L&R C. Не было б в заголовке слова "новая" -- не было бы вопросов. Лучше было б, наверное, "современная" вместо "новая". Ну или что-то в этом роде -- в общем, чтоб не создавало иллюзий, будто речь пойдёт о C++23 или, на худой конец, С++20.

Не буду спорить про точность формулировок, но для меня статья оказалась полезной: я с C++ лет 20 толком не работал (последние 10 лет пишу на Python, до того - PHP, а до того - Java и только где-то на горизонте маячит C++) и недавно обнаружил, что он изрядно изменился. Просто этот язык довольно долго не был "моим", а тут решил, что он мне снова нужен. Статья как раз для таких, как я, кому надо быстро понять, что поменялось и не пытаться изучать язык совсем уж с нуля, чтобы просто читать чужой код.

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

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

Я в курсе, спасибо, последние 10 лет пишу на плюсах практически ежедневно. Проекты масштабами в миллионы строк кода. Да, некоторые усилия для смены -std=c++-N потребуются, но они бесконечно далеки от "всё переписать".

Думаю, тут имелось в виду, что появился повод потратить на рефакторинг с наведением современной красоты некоторое, достаточно большое, количество времени. Чтобы не пришлось потом совсем уж с нуля переписывать по причине окончательной потери совместимости с новыми стандартами. А то я с таким сталкивался, пусть и не в проекте с С++, компилятору которого можно просто указать версию стандарта, а когда взялся дописать "пару функций" в проект на Python/Django, а у Django, как оказалось, к тому времени дважды сменилась major-версия, по сравнению с тем, что крутилось на сайте.

UFO just landed and posted this here

некоторые программисты родились позже

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

Переходят или не ререходят не люди, а проекты и/или организации.

Я почитал статью, она как раз для меня. Для меня C++ это язык, которым я пользовался только в университете и писал там на C с классами

вам походу забыли объяснить что это разные языки

Для иллюстрации lambda и move семантики я бы предложил другие примеры. На моей практике они заметно чаще встречаются

Lambdas - сортировка со своим предикатом

std::vector<A> as = ...;
auto compare = [](const A& left, const A& right) { return left.x < right.x; };
std::sort(begin(as), end(as), compare);

Move семантика - возврат контейнеров по значению теперь вполне идиоматичен:

std::vector<A> getAs(...);
auto as = getAs(); // vector is moved (not copied) which is cheap

Второй пример это не столько move сколько RVO/copy elision

RVO/NRVO - оптимизация, которой может и не случиться. А вот move сработает гарантированно

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

Move-семантика только один из механизмов, обеспечивающих идиому возврата дорого копируемых объектов по значению, и если изначально она была ключевой, то с C++17 отошла на второй план и даже в определенных случаях стала антипаттерном (когда стандарт гарантирует RVO, ухудшает производительность).

Не очень понял про антипаттерн. Слово антипаттерн подразумевает, что есть способ писать код, как делать не надо. Можете привести пример?

Могу предположить что не нужно делать `return std::move(x)`, но это и до C++17 считалось чушью

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

Спасибо за статью!

Больше C++, хорошего и разного.

Но вот в разделе про move - семантику, кмк, упустили самое важное - суть этого нововведения, ограничившись какими-то волшебными примерами "перемещения".

А суть в общем-то проста - это именно семантика, позволяющая писать разный код для работы со временными и "долговременными" объектами, rvalue и lvalue. Именно благодаря знанию о сущности аргумента оператора присваивания или конструктора можно написать гораздо более эффективный код, в случае того же std::vector'а позволяющий просто(* за редкими исключениями наподобие несовместимых аллокаторов) обменять указатели на реальный массив вместо честного поэлементного копирования.

До сих пор убивает это наследние C -

void f(int &a) {...}

Тип a - это int&, но можно писать (и часто пишут) & рядом с a, а не с int.

Здесь мы только слегка коснулись огромного набора новых фич

А вот тут самая фишка - огромное количество уже существующих и новых и новых фич. Язык становится монструозным.

UFO just landed and posted this here

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

Я тоже считаю, что писать надо int& a. Но всегда найдутся староверы с int &a. В любом случае надо следовать правилам принятым в конкретном проекте, имхо.

Вроде как по последним веяниям так

int &a, &b;

писать нехорошо.

Надо так

int &a;

int &b;

И вопрос о том, куда лучше прижимать &, в результате остается открытым.

Да и собака - это вроде @.

В какой-то момент стал отбивать амперсанд вообще с двух сторон и писать const после типа:

auto & var;
auto const& var; 
for (auto const& [k,v]: map) { ... }

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

Но вообще, синтаксис Си отвратителен, а синтаксис объявлений -- отвратителен вдвойне.

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

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

вот насколько я знаю есть смысл различать:

int & a

int * a

int ? a

и применять адекватно в зависимости от контекста. А где там пробелы стоят важно будет ну может быть только с большого программистского бодуна или ...

для тех кто не хочет признаться что не в состоянии понять разницу между

int & a

int * a

int ? a

Вопрос скорее в том, насколько интуитивно читать объявление типа "int& a, b;"

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

Потом вот вы написали:

int& a, b;

вас видимо не беспокоит что оно даже не скомпилируется.

должно быть:

int& a = something;

насколько я помню, соответственно Б туда не влезет. НО -

кого это волнует, главное пробелы правильно расставить.

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

Стесняюсь спросить, а что такое

int ? a

?

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

>Язык становится монструозным.
ТОЛЬКО СТАНОВИТСЯ?
---
Чак Норрис программирует на С++. На С++2099.

Чак Норрис не программирует на С++2099. При виде Чака Норриса, С++2099 программирует себя сам.

Согласен, что язык монструозный.

int a = 0, b = 0;
int &c = a, d = b;

Если не ошибаюсь, то c - ссылка на переменную a. В b - переменная типа int. И вот в таком случае рядом с чем писать символ "&" ? (В реальной жизни никогда бы так не написал, что-бы не ввести в заблуждение).

Ещё раз - это жуткое наследие С и вообще не надо так писать.

Правильно надо так


typedef int& int_ref;

или по сиплюсплюсному


template<typename T> struct ref { typedef T& type; };
//...
void fn( ref<int>::type a );

Т.е. от 5-9 значащих символов на объявление переменной к почти сотне. Отличный прогресс! Таким языком обязательно все будут восхищаться!

template<typename T> struct ref { typedef T& type; };

Э...

template<typename T>
using ref = T&;

Выглядит как то неконсистентно с std::enable_if или std::remove_cv. Да и вообще зачем изобретать велосипед, уже есть std::add_lvalue_reference

Т.е. вы предлагаете писать:

void fn(std::add_lvalue_reference<int>::type a) {...}

Не уверен, что это финальный вариант, но по крайней мере похоже на современный C++ )

O_o. У меня современный C++, наверное, какой-то другой системы :)

Я в принципе не понял, зачем @kovserg ввел тип ref. Вероятно, в каких-то сценариях это полезно. Но, имхо, сделано это было каким-то замороченным способом, можно было бы и проще.

Я в принципе не понял

Похоже, вы в принципе шуток не понимаете )

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

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

Да и вообще разбивать код на строчки некрасиво, лучше всё писать до 120-ой колонки - так будет компактнее.

Кстати дефолтовый конструктор это совсем не пользовательский пустой {}. Например дефолтовый проделает zero-initialization своих мемберов, а пустой уже не сделает этого.

А чем =default отличается от отсутствия явно задекларированного конструктора (в классе/структуре без других конструкторов, как в примере в статье)?

Конкретно в том примере непонятно зачем оно там.

UFO just landed and posted this here

Предположим что внутри, как в примере (я ж в к примеру придираюсь, а не к ключевому слову как таковому).

Разве что тем, что указание default явно говорит, что конструктор не забыли)

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

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

Кроме того, как говорилось выше, функции-члены могут быть объявлены без = default, а определены именно с = default. Таким образом в будущем можно будет написать свою реализацию - это способствует минимальной перекомпиляции клиентов и бинарной совместимости.

Я же чётко написал - вопрос конкретно к примеру в статье, когда никаких других явно объявленных конструкторов нет. С тем, что в общем случае default полезен, не спорю.

Вот именно. Хотел чел написать кастомные конструктор/деструктор, но забыл. А компилятор молча сгенерил дефолтные. Баг налицо.

Если дефолтный не нужен, то пригодится скорее deleted.

Ок, default явно говорит, что хотели именно дефолтный. Для ясности.

нет, конструктор всегда лучше делать = default, а вот

int i;
и
int i{};
различаются, в первом случае неинициализировано, во втором 0

Это кажется касается С в первую очередь, не С++. nullptr_t появился в 11 стандарте, поэтому возможно тогда же и provenance завёлся.

Итак, судьба снова свела вас с C++,

Я не хотел этого!

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

Нет. Нет. Нет.

Выскажу непопулярное мнение.

Сообщество C++ дополняет стандарт чаще, чем Apple выпускает новые iPhone. Благодаря этому C++ теперь больше похож на большого слона, а съесть целого слона за один присест невозможно.

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

И порой возникает ощущение, что стандарт развивается быстрее, чем компиляторы.
По мне так лучше было бы развивать тот же LLVM и возможности линковать в одну программу модули на разных языках. Удобнее написать этот кусок на Rust, Go или чем-то еще? Пишем на то, на чем удобнее и линкуем модулем.

Когда-то очень нравился pure C за его простоту, лаконичность и почти неограниченные возможности (да, там легко было выстрелить себе в ногу, но на то и дана голова чтобы этого не делать).

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

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

      dcl-ds t_dsArray qualified template;
        Array  char(70) dim(arrSize) ascend;
          Key  like(t_Customer)  overlay(Array: 1);
          Data like(t_CustData)  overlay(Array: 10);
          CUS  char(6)           overlay(Array: 1);
          CLC  char(3)           overlay(Array: 7);
          CPNC char(6)           overlay(Array: 10);
          CUN  char(35)          overlay(Array: 16);
          CRF  char(20)          overlay(Array: 51);
      end-ds;

Array - массив строк по 70 символов. Каждая строка структурирована - первые 6 символов CUS, потом три символа CLC, 6 символов CPNC, 35 символов CUN и 20 символов CRF. Кроме того, CUS + CLC рассматривается как ключ (Key), остальное - как связанные с ним данные (Data).

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

            cntCustomers += 1;
            
            dsArray.CUS(cntCustomers)  = dsGF.GFCUS; 
            dsArray.CLC(cntCustomers)  = dsGF.GFCLC;
            dsArray.CPNC(cntCustomers) = dsGF.GFCPNC;
            dsArray.CUN(cntCustomers)  = dsGF.GFCUN;
            dsArray.CRF(cntCustomers)  = dsGF.GFCRF;

А дальше - хотим работаем со всей строкой целиком, хотим - с отдельными ее элементами.

Да, на С++ все это можно сделать - позволяет. Без проблем. Но придется написать свой класс, с конструктором, методами и т.п. А тут все просто на уровне определения структуры данных.

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

Опять же, мое личное мнение, без претензий на абсолютную истину.

Спасибо утащил к себе в фонд цитат и в рамочку и на стену "..Это проблема. Просто потому, что "сообщество" пытается втащить в С++ все, что увидело в других языках..". Самая точная характеристика этих частых стандартов

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

Как пример - появились корутины (Go, C# и т.п.) - надо обязательно втащить их в С++ в очередном стандарте... И покопаться - такого сплошь и рядом.

Последние 6 лет работаю в банке, платформа IBM i (центральные сервера, ядро АБС, вся бизнес-логика). Основной язык - RPG (ровестник Кобола, тоже для коммерческих вычислений, но до сих пор активно развивается и сейчас это вполне полноценный процедурный язык). Пример описания структур выше - из него. Там вообще много интересного есть на уровне языка.

Но суть не в этом. Суть в том, что на платформе реализована концепция ILE - интегрированная языковая среда. Это значит что можно написать модуль на С/С++, модуль на RPG и объединить их в один программный объект ("модуль" здесь есть аналог объектного файла). Главное правильно прототипы функций реализованных на исходном языке модуля описать на целевом языке откуда они будут вызываться:

extern "C" _RPG_ind USRQ_Create(char* __ptr128 Name, char* __ptr128 Lib, eQueueType eSeq, 
                                char* __ptr128 Desc, int nMsgSize, int nKeyLen, int nInitMsgs, 
                                int nExtMsgs, int nMaxMsgs, char* __ptr128 pError);

Это исходная функция на С (на __ptr128 не обращаем внимание - это заморочка, связанная с разными моделями памяти - в одной указатели 128 бит, в другой - 64 бита)

extern "C" тут просто чтобы с манглингом не заморачиваться (хотя и это возможно - просто придется правильное имя в extproc прописывать)

dcl-pr USRQ_CreateQueue ind extproc(*CWIDEN : 'USRQ_Create') ;
  Name      char(10)                   const;                                  // Имя очереди
  Lib       char(10)                   const;                                  // Библиотека
  eSeq      int(10)                    value;                                  // Тип очереди queKeyd/queLIFO/queFIFO
  Desc      char(50)                   const;                                  // Описание
  nMsgSize  int(10)                    value;                                  // Макс. размер сообщения
                                                                               // Максимально допустимое значение - 64000 байт
  nKeyLen   int(10)                    value;                                  // Размер ключа (игнорируется для не queKeyd)
                                                                               // Максимально допустимое значение - 256 байт
  nInitMsgs int(10)                    value;                                  // Начальное количество сообщеий
  nExtMsgs  int(10)                    value;                                  // Колчество сообщений в приращении
  nMaxMsgs  int(10)                    value;                                  // Максимальное количество сообщений
  Error     char(37)                   options(*omit);                         // Ошибка
end-pr;

И ее прототип на RPG позволяющий вызывать ее из RPG модулей.

Более того, из RPG можно вызывать любую функцию из С-шной библиотеки просто описав ее протип.

Например, в RPG не завезли генератор ПСЧ. Ну и что? Пишем:

      dcl-pr Random int(10) extproc('rand');
      end-pr;

и пользуемся

rndIdx = %rem(Random(): upLimit) + 1;

И вот работа в таких условиях вполне комфортна - бизнес-логику удобнее писать на RPG, но если нужно что-то низкоуровневое (или просто удобнее и эффективнее на С сделать) - пишем на С.

На других платформах нечто аналогичное реализовано в LLVM (и даже в более широких пределах). Такой подход позволяет не тянуть все в один язык, но пользоваться одновременно несколькими языками комбинируя преимущества каждого из них и нивелируя недостатки. Лично мне такой подход кажется более гибким и мощным.

Как пример — появились корутины (Go, C# и т.п.) — надо обязательно втащить их в С++ в очередном стандарте… И покопаться — такого сплошь и рядом.

Да, надо было. На самом деле это надо было сделать с самого начала. Сопрограммы — довольно древняя концепция, и их совершенно зря позабыли при создании ЯВУ. Кстати, двигались они по языкам довольно медленно, заново появились-то они впервые в Python 2.2 в 2001м году.


на платформе реализована концепция ILE — интегрированная языковая среда

Судя по тому, что вы пишете дальше, это фича не столько Си/С++, сколько языка RPG.


И я не вижу никаких принципиальных отличий от FFI, реализованного в Rust, Java, C#, Go и Haskell. Вот буквально всё написанное вами делается в любом современном языке.

Судя по тому, что вы пишете дальше, это фича не столько Си/С++, сколько языка RPG.

Нет. Это фича платформы. Все компиляторы тут (CL, COBOL, RPG, C/C++) на выходе дают универсальный TIMI код (код в т.н. "машинных инструкциях" - это не ассемблер, выше уровнем, ближе к набору низкоуровневых системных команд и функций). И биндеру (линковщику) потом уже все равно на каком языке это было изначально написано.

Это ровно то же самое, что реализовано в LLVM.

Рад за них, но на других платформах FFI прекрасно работает и без универсального кода.


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

принципиальных отличий от FFI, реализованного в Rust, Java, C#, Go и Haskell.

Потому что везде это старый добрый С ABI и пока никто ничего принципиально нового не изобрёл. В расте на свой ABI забили, а в WASM пока ещё WIP, хотя местами и есть разные черновые варианты.

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

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

Такое интервью :)


Интервью Bjarne Stroustrup

HACKNET REVIEW 01/98
Интервью Bjarne Stroustrup, данное 1 января 1998 года
для журнала Computer.
© 1998, Computer
перевод: Mike Bluesman


 Первого Января 1998 года Bjarne Stroustrup давал интервью журналу 'Computer'. Вообще-то редакторы предполагали, что он расскажет о семи годах объектно-ориентированного программирования с применением языка, который он и разработал.
 К окончанию беседы выяснилось, что интервьюер извлек больше информации, чем предполагал, и, естественно, редакторы решили урезать содержание 'для пользы индустрии', но, как обычно получается в таких случаях, произошла утечка информации.
 Вот полный и нередактированный протокол интервью - это не похоже на обычные запланированные вопросы/ответы.
Вам наверняка покажется это интересным.

Интервьюер — далее И., Stroustrup — далее C..


И. Прошло несколько лет с тех пор, как Вы изменили мир разработки программного обеспечения. Что Вы теперь чувствуете, оглядываясь назад?


C. Вообще-то я думал об этих днях как раз перед тем как Вы приехали. Помните — все писали свои версии 'C', и проблема была в том, что все это делали чертовски замечательно. Университеты тоже чертовски замечательно преподавали этот язык. Это привело к понижению компетенции. Под 'компетенцией' в данном случае я подразумеваю феноменальность. Вот что породило проблему.


И. Проблему?


C. Да, проблему. Помните когда все писали на Cobol?


И. Конечно, я тоже это делал.


C. Ну вот, в начале эти ребята были как боги. Им платили кучу денег и относились как к королям.


И. Да уж, вот это были времена...


С. Именно. Ну и что же случилось? IBM прямо заболела этим и вложила миллионы в подготовку программистов, пока их не стало до ужаса много.


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


С. Точно. То же самое случилось и с программистами, писавшими на 'C'.


И. Понятно, ну и что же Вы все-таки хотите этим всем сказать?


C. Однажды я сидел у себя в оффисе, и мне пришла в голову небольшая идейка, как хоть немного восстановить баланс. Я подумал: интересно, что произойдет, если будет язык программирования такой запутанный и такой сложный для изучения, что никто бы уже не сможет заполнить рынок толпой программистов, пишуших на этом нем? У меня уже были тогда кое-какие мысли по этому поводу. Вот, знаете наверно, X10 и X windows. Это тогда была такая графическая система, которая работала на Sun 3/60. У нее были все ингредиенты, которые мне были нужны — комплексный синтаксис, сложные для понимания мрачные функции, псевдо объектно-ориентированная структура. Даже сейчас никто не пишет напрямую под X-windows. Motif — единственный путь, если вы хотите сохранить рассудок.


И. Шутите?


C. Ничуть. Есть еще одна проблема. Unix был написан на 'C' — это значило то, что любой программист, пишущий на 'C', мог очень легко стать системным программистом. Помните сколько обычно зарабатывали большинство системных программистов?


И. Да, я же ведь тоже этим занимался.


С. Так вот, этот новый язык должен был отделять себя от Unix путем скрывания всех системных вызовов, которые так здорово связывают 'C' и Unix. Тогда ребята, знающие только DOS, тоже смогли бы прилично зарабатывать.


И. Не верится в то, что Вы это сказали...


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


И. Ну расскажите поточнее, как же Вы все-таки сделали это?


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


И. Что?


С. И относительно 'повторно-используемого кода' — Вы когда-нибудь слышали, чтобы хоть одна компания 'повторно-использовала' что-либо?


И. Ну, вообще-то не слышал, но...


С. Вот так-то. Некоторые, кстати, пытались. Была такая компания из Орегона — Mentor Graphics, в которой просто заболели тем, что пытались переписать все что можно на C++ в '90 или '91 году. Я на самом деле им сочувствовал, но думаю, что люди по крайней мере, научились чему-то на их ошибках.


И. Очевидно у них ничего не вышло?


С. Вообще ничего. Но было бы сложно объяснить держателям акций компании ущерб в 30 миллионов долларов и вот, надо отдать им должное, они все-таки заставили это работать в итоге.


И. Так все-таки у них получилось? Это доказывает что 'объектное-ориентирование' работает.


C. Почти. Запускаемый файл получился такой огромный, что загружался 5 минут на рабочей станции HP со 128Mb оперативной памяти. Я думал, что это станет камнем преткновения, но это никого особенно не заботило. Sun и HP были очень рады продавать до ненормальности мощные ящики с огромными ресурсами для выполнения на них тривиальных программ. Знаете, когда мы в AT&T; откомпилировали нашим первым компилятором C++ программку 'Hello World', я не мог поверить своим глазам: запускаемый файл получился размером 2.1Mb.


И. Да уж… Но компиляторы с тех пор прошли долгий путь.


C. Вы так думаете? Попробуйте тот же пример 'Hello World' с последней версией g++ — вы получите примерно пол-мегабайта. А кроме этого есть еще множество примеров со всего мира. У British Telecom чуть было не возникли большие проблемы, но к своему счастью они вовремя догадались свернуть проект и начать все заново. И им больше повезло, чем Australian Telecom. А теперь я слышал, что Siemens cоздает какого-то динозавра и все больше и больше волнуется по поводу размера того, что у них получается. Не правда ли забавно смотреть на это всеобщее заблуждение?


И. Да, но C++ -то, в общем, вполне нормальный язык.


С. Вы в это так верите? Попробовали ли вы когда-нибудь сесть и поработать над проектом на C++? Во первых, я расставил достаточно ловушек, чтобы просто так работали только тривиальные проекты. Под конец проекта получается что одни и те же операторы в разных модулях означают совершенно разные вещи. А теперь попробуйте соединить все эти модули в единое целое, особенно если у вас их штук 100. Боже, я иногда не могу удержаться от смеха, когда слышу о проблемах разных компаний, которые не могут сделать так, чтобы их модули общались между собой.


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


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


И. Как это?


С. Не понятно что-ли? Помните typedef ?


И. Конечно.


С. А теперь вспомните сколько времени приходится копаться в заголовках для того, например, чтобы просто найти, что какое-нибудь там 'RoofRaised' — число с двойной точностью. Представьте теперь сколько времени уйдет на нахождение всех определений типов в большом проекте.


И. Значит, Вы утверждаете, что Вам все, что Вы хотели удалось...


C. Ну, вспомните сколько занимает реализация проекта среднего размера на 'C'. Это около 6 месяцев. Не достаточно долго чтобы парень с женой и детьми мог заработать себе на нормальное существование. Попробуйте тот же проект реализовать на C++, и что получится? Вам понадобится 1-2 года. Не правда ли, это замечательно? Кроме этого: в университетах уже так давно не преподают 'C', что теперь стало мало людей программирующих на 'C', особенно таких, которые знают все о программировании под Unix. Как вы думаете: сколько парней смогут сообразить что делать с 'malloc', после того как втечение многих лет они пользовались 'new' и никогда не заботились о проверке кода возврата? Большинство программистов на C++ вообще не выбрасывают этот код возврата. Что произошло со старой доброй '-1'? По крайней мере было сразу понятно, что у тебя где-то ошибка без всяких там 'throw', 'try' и 'catch'...


И. И все же, наследование экономит кучу времени?


С. Нет, я же говорил… Замечали, в чем разница между стадиями планирования проектов на 'C' и C++? Для проекта на C++ эта стадия в три раза дольше. Время уходит на то, чтоб убедиться что все что надо наследуется, а все что не надо — нет. И все-равно без ошибок не обходится. Кто слышал когда-нибудь об утечке памяти в программе на 'C'? Теперь нахождение этих утечек — целый труд. Большинство компаний сдаются, так и выпускают продукт, зная что утечка памяти существует.


И. Но есть различные программные инструменты...


С. Большинство из которых написаны на C++.


И. Если мы опубликуем все это, то Вас просто могут линчевать, понимаете ?


C. Сомневаюсь. Как я сказал C++ уже уходит в прошлое. Ни одна компания без предварительного тестирования теперь не начнет проект на C++, а если будет тестирование, то они поймут, что это путь к неудаче. Если не поймут — то так им и надо. Знаете, я пытался убедить Dennis'a Ritchie переписать Unix на C++.


И. О Боже. И что же он сказал?


C. К счастью у него присутствует хорошее чувство юмора. Я думаю и он, и Brian понимали что я тогда делал. Он ответил, что может мне помочь написать версию DOS на C++, если я захочу.


И. Ну и как? Вы захотели?


С. Я написал DOS на C++. Могу дать вам demo. Она у меня работает на Sparc 20 в другой комнате. Просто летает на четырех процессорах и занимает всего то 70 мегабайт на диске.


И. На что же это похоже на PC ?


С. Вы, очевидно, шутите. Видели же вы Windows'95? Я о них думаю как о своем величайшем успехе.


И. Знаете, эта идея насчет Unix++ заставила меня задуматься. Ведь где-то может сидеть парень, которому придет в голову сделать это...


С. Но не после того, как он прочитает это интервью.


И. Я сожалею, но врядли мы сможем опубликовать даже часть этого интервью.


С. Но это же история века. Я просто хотел чтоб мои приятели-программисты помнили меня за то, что я для них сделал. Знаете как сейчас оплачивается программирование на C++ ?


И. Последнее, что я слышал — настоящие профессионалы зарабатывают $70-80 в час.


С. Понимаете теперь? И я уверен, что он заслуживает этих денег. Отслеживание всех этих ловушек, которые я встроил в C++ — не легкая работа. И, как я говорил раньше, каждый программист на C++ чувствует себя связанным тем обстоятельством что он должен использовать каждый элемент языка в каждом проекте. Вообще это и меня часто раздражает, даже тогда, когда это служит моим целям. Но сейчас, когда прошло столько времени, мне уже начинает нравиться этот язык...


И. Имеете ввиду, что раньше Вам C++ не нравился?


С. Ненавидел его. Он даже выглядит неуклюже, вы не согласны? Но когда стали там выходить разные книги… вот, тогда-то я и увидел полную картину.


И. Погодите, а как насчет ссылок? Вы подтверждаете что улучшили указатели 'C' ?


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


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


С. Пообещайте мне, что опубликуете это.


И. Я извещу Вас, но мне кажется, что я знаю, что скажет мой редактор по этому поводу.


С. А все-равно, кто этому поверит? Кстати, не могли бы вы мне прислать копию этой записи?


И. Это я могу.


Примечание переводчика :


Я не программирую на C++. Я не являюсь знатоком русской словестности. Посему прошу извинения за возможные ошибки в переводе.


специальный перевод для Hacknet Review выполнил Mike Bluesman, март 1998

"Этих частых стандартов"? Смешно.
Посмотрите хотя бы на версии питона, и поймёте, что комитетчики - те ещё лютые тормоза.
И кстати, комитетчики лихо отбиваются от разных пропозалов "я это видел в другом языке (в хаскелле, например!) и хочу такое же тут". В стандарт попадают довольно консервативные вещи, иногда это даже бесит - три года обсуждали гору, родили мышь.

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

Это проблема не только C++, но и практически всех популярных языков. Посмотрите C# - сколько там парадигм добавили помимо деревьев выражений. Даже JS во что превратился... Может разве что Python более консервативен, но с ним не приходилось работать.

современный С++ проигрывает чистому С в производительности и использовании ресурсов

И следом несколько пруфов...

Как нет?

(и постоянное динамическое выделение памяти не всегда хорошо и исключения не всегда быстро)

И следом несколько пруфов...

Как нет?

Например

И тут пример, на котором проиграют (практически) все универсальные языки, включая упомянутый C, а так же Java, C#, Python, JavaScript и т.д.

Но претензию нужно предъявить C++, да.

Пруфы, увы, привести не смогу - вряд ли кому-то здесь что-то скажут данные, полученные из PEX (Performance EXplorer) Так что придется поверить на слово.
Нагрузочное тестирование у нас очень жесткое (т.к. hiload) и насмотрелся на то, как слишком активная динамическая работа с памятью отъедает ресурсы процессора.

До банка работа в АСУТП. И там приходилось в некоторых местах отказываться от удобных и универсальных stl реализаций в пользу самописных, заточенных под конкретную задачу. Просто потому что любая универсальность притащит хоть немного, но оверхеда. И готовые решения просто "не вывозили" по скорости.

Аналогично с исключениями. Пока они работают в рамках одного объекта - все ок. Как начинаем выводить их за рамки объекта (где все это реально нужно) - получаем просадку в производительности (по PEXам опять же). И в добавок ко всему, языковые средства обработки исключений работают только с языковыми исключениями и не ловят системные. И зачем оно мне, когда у меня есть возможность напрямую, средствами системных API кинуть в любом месте системное исключение и средствами системных же API его перехватить и обработать там, где это реально нужно?

Но претензию нужно предъявить C++, да.

Увы, но Вы меня неправильно поняли. Я не предъявляю претензий. Просто высказал мнение, что есть подходы более гибкие и мощные, чем тащить все в один язык, постоянно модифицируя его стандарт (настолько быстро, что он уже теряет смысл стандарта как некоего неизменного документа).

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

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

Есть стандарт на ваши изделия. Вы работаете строго по нему. И в один прекрасный момент заказчик Вам заявляет — "я не приму вашу продукцию т.к. она не соответствует новому стандарту".

Это проблемы разработчиков компиляторов. И, в общем-то, именно они и принимают новые стандарты. Так в чём же проблема-то?

Так что придется поверить на слово.

Ну прямо классика: "А у нас джентльменам верят на слово. И тут мне карта как поперла!"
Видите ли, мне довелось увидеть достаточно C++ного кода, написанного в стиле:

std::string * v = new std::string(...);
...
delete v;

или:

void f(std::string v) { // Передача по значению, да.
  ... // Здесь v только читается и никуда не копируется/переносится.
}
std::string some_value = ...;
f(some_value);

чтобы со скепсисом относиться к утверждениям про тормознутость С++.

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

И переписывали вы "stl реализации" на чем, неужели на чистом Си?

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

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

Пока они работают в рамках одного объекта - все ок. Как начинаем выводить их за рамки объекта (где все это реально нужно) - получаем просадку в производительности

Складывается ощущение, что у вас какие-то странные представления об исключениях. Даже в рамках одного объекта (что бы это не означало) исключения вам просадят производительность в разы, а то и на порядок-другой, если начнут выскакивать чаще, чем в 1-2% случаев.

И зачем оно мне, когда у меня есть возможность напрямую, средствами системных API кинуть в любом месте системное исключение и средствами системных же API его перехватить и обработать там, где это реально нужно?

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

Просто высказал мнение

Высказали для чего? Просто облегчить душу? Если так, то OK, выплеснули накопившееся, вам полегчало и все, расходимся.

Но если это не так, то как на ваше мнение нужно реагировать?

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

Э... Даже если оставить в стороне тот факт, что тенденция создавать среду, в которой можно бесшовно использовать несколько языков, развивается уже лет 30, наверное (только на моей памяти начиная с появления в Windows OLE, которое потом стало COM). И на современные результаты можно посмотреть в экосистемах .NET и JVM. Даже не смотря на это, есть вот какая сторона медали:

даже если вы пишете на языке X в среде, где запросто могут уживаться еще и языки Z, Y, W, то все равно остается вопрос о том, на насколько удобно/просто/безопасно/эффективно писать именно на X, если таки нужно писать именно на X?

Вот нужно вам использовать C++ (не суть важно почему) и все. Вот нужно.

И что, вы готовы еще лет 20 программировать на C++98? Или даже на C++11?

Я нет. Мне даже на C++14 после C++17 возвращаться бывает тяжело (if constexpr нет, structured bindings нет, [[nodiscard]] нет, ...). А уж застрять на C++98... Мне столько не заплатят, чтобы добровольно согласиться на такое.

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

Ну а мене не заплатят столько чтобы я кинулся на С++ реализовывать работу со сложными структурами данных (приводил пример - и это еще достаточно простой случай). Или проверку корректности даты в каком-нибудь формате типа *CYMD когда я просто могу написать:

test(de) *cymd var;
if %error();
  // это не дата а непойми что
endif;

или ждать пока это вдруг войдет в С++ 3999-го года.

Причем, вместо *cymd может быть любой другой формат - *eur, *iso и т.п.

Но если это не так, то как на ваше мнение нужно реагировать?

Можно никак не реагировать :-)

даже если вы пишете на языке X в среде, где запросто могут уживаться еще и языки Z, Y, W, то все равно остается вопрос о том, на насколько удобно/просто/безопасно/эффективно писать именно на X, если таки нужно писать именно на X?

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

Вот нужно вам использовать C++ (не суть важно почему) и все. Вот нужно.

На мой взгляд это ошибочная парадигма. Мне платят за решение конкретной задачи наиболее эффективным образом, а не за то, чтобы она была написана на каком-то определенном языке. Заказчика абсолютно все равно на чем она написана. Хоть на китайском, хоть на древнешумерсокм. Заказчик выставляет требования к функционалу и граничные условия (временное окно, загрузка процессора). И принимает задачу по соответствию поставленным требованиям.

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

Как часто вам приходится писать код, который должен работать на нескольких платформах? Мне вот за... (сколько там с 91-го года прошло?) фактически ни разу не приходилось. Разработка всегда велась под конкретную платформу с максимально эффективным использованием ее особенностей и возможностей.

Ну есть у меня код для работы с очередью *USRQ на IBM i. Написан на С. Сможете его собрать под виндой? Нет. По Линуксом? Нет. ни там ни там и близко нет сущности, похоже на *USRQ. Но при этом объект очень полезный и удобный для реализации различного рода конвейеров (да, можно использовать сокеты, пайпы, майлслоты, но там возможностей вполовину меньше - сразу проигрываем в фоункционале)

Т.е. вся "совместимость языка" быстро упирается с несовместимость платформ.

Да, С/С++ реализованы на всех практически платформах, но это совершенно не значит что код, написанный для одной, будет точно также работать на другой. Ну или придется использовать очень ограниченный набор возможностей и/или кучу #ifdef'ов

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

Складывается ощущение, что у вас какие-то странные представления об исключениях. Даже в рамках одного объекта (что бы это не означало) исключения вам просадят производительность в разы, а то и на порядок-другой, если начнут выскакивать чаще, чем в 1-2% случаев.

Это нормально?

На мой взгляд нет. Исключение - механизм обработки нештатной ситуации. Именно нештатной. Которая совершенно не обязательно ошибка. Как пример - для той же *USRQ есть инструкция deqwait - чтение из очереди с таймаутом. Если очередь пуста - выбрасывается исключение. Системное, не языковое. Средствами С++ не ловится. Только установкой exeption handler. Дальше смотрим по коду исключения и видим что ничего страшного не произошло, просто истекло время отведенное на чтение, но ничего не прочиталось.

Ну а мене не заплатят столько чтобы я кинулся на С++ реализовывать работу со сложными структурами данных

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

Я бы еще понял, если бы вы пеняли на сложность работы с графовыми структурами (в которых есть циклы); со структурами, где требуется сопоставление с образцом (pattern-matching); с рекурсивными типами (вроде бы не напутал с названием), что-то вроде:

struct A {
  A left;
  A right;
  ...
};

и т.д., и т.п.

Или проверку корректности даты в каком-нибудь формате типа *CYMD когда я просто могу написать:

Э... А что хотели показать-то? Что в C++ подобное нельзя написать? Или что этого нет в stdlib?

Ну так в C++ в stdlib, криптографии, например, нет. Как и средств работы с сетью или shared memory. Что не говорит о том, что подобные вещи в C++ не используются.

Можно никак не реагировать

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

Суть в том, что нет нужды писать все только на Х.

Это понятно. Но если таки нужно использовать X, то должен ли X становиться лучше со временем, или же как отлили 30 лет назад в граните, так и должен мхом покрыться?

На мой взгляд это ошибочная парадигма.

Что вижу вокруг, о том и пою. Задачи могут быть разными, начиная от "у нас старый C++ный код, который падает, течет и тормозит, нужно привести в чувства" и заканчивая тем, что вам нужно сделать свой аналог условного Photoshop или nginx, и альтернатив у C++ в предметной области будет раз-два и все.

Заказчика абсолютно все равно на чем она написана.

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

Ну и следуя такой логике комментарии в коде писать не нужно: работает же и это главное.

Как часто вам приходится писать код, который должен работать на нескольких платформах?

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

Ну или придется использовать очень ограниченный набор возможностей и/или кучу #ifdef'ов

Ну придется, и что? Абстракции тут в помощь.

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

Звучит как "а языка-то я толком и не знаю".

Это нормально?

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

UFO just landed and posted this here

Стыковать код, использующий разные реализации словарных типов, тяжело и неэффективно.

Да.

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

Не могу судить.

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

Так же не хочется уходить в ту сторону, что std::string или std::optional -- это словарные типы до тех пор, пока мы находимся в рамках C++ (да и то пока мы не имеем дела с DLL, в каждой из которых своя копия stdlib и свой хип). Как только появляется вопрос интеграции C++ с кодом на других языках, как выяснится, что родной для C++ std::string отнюдь не родной для какого-нибудь Python или Ruby.

Я все-таки заострю внимание на другом: в stdlib C++ есть немного вещей, для которых нужна поддержка компилятора и которые просто так на свои не заменишь (емнип, std::launder тому пример). Вот в таких местах мы жестко привязаны к stdlib. Тогда как значительную часть STL можно запросто проигнорировать если по каким-то причинам нас эта часть не устраивает.

UFO just landed and posted this here

В общем я тоже не могу, но по личному опыту — не-инвалидация ссылок и итераторов после ребалансировки мне не нужна была практически никогда, а вот код с unordered_map был виден в профайлере нередко.

Могу лишь предполагать, что на такое поведение пошли для того, чтобы unordered_map был легкой заменой для std::map. Типа был std::map, хлоп, заменили его на unordered_map и не нужно потом блох по коду ловить.

Ну да это офтопик. ЕМНИП, доводилось слышать претензии даже к std::vector.

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

Мне очень не нравится критиковать действия комитета, поэтому воздержусь.

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

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

И вот тут бы остановиться, все остальное уже развивать в виде нового языка, отринув обратную совместимость со старым С. Просто новый язык на базе С++. Но нет...

Так вроде же есть же? По мне так C# как раз новый язык на базе С++, или нет? Кто скажет против? Интересно почитать опровержения по такому утверждению!

Или нет. C# - это язык на основе Java, вообще-то. Исторически. К тому же ещё и "корпоративный", как Objective-C и Swift. Только недавно "открылся" для других платформ.

Или вы все "С-подобные" языки относите к языкам на основе "С++"?

C# - ... Только недавно "открылся" для других платформ.

Так открылся же, какая разница когда? Получается теперь какбы и не "корпоративный".

Или вы все "С-подобные" языки относите к языкам на основе "С++"?

А что с этим не так? по моему это логично, относить все "С-подобные" языки к языкам на основе "С++"

на основе Java, вообще-то. Исторически

исторически вы имеете ввиду что позже появился? а на что это "исторически" влияет?

Так открылся же, какая разница когда? Получается теперь какбы и не "корпоративный".

Ну так протолкнёте какую-нибудь полезную фичу? Без Микрософта? Был и есть корпоративный: запретят завтра всем на нём программировать - никуда не денемся.

по моему это логично, относить все "С-подобные" языки к языкам на основе "С++"

Ну как бы смешивать понятия синтаксиса и языка ... неумно, что ли?

исторически вы имеете ввиду что позже появился?

Нет. Значит в истории развития языков довольно чётко записано , что С# появился именно как аналог Java, после разборок Microsoft и Oracle.

а на что это "исторически" влияет?

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

Ну как бы смешивать понятия синтаксиса и языка ... неумно, что ли?

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

Значит в истории развития языков довольно чётко записано ...

Кем записано, когда записано, где это можно прочитать?

Насколько я помню история это НЕ точная наука, там "четко" записано несколько вариантов обычно, я так понимаю, что у вас есть только один, который вы изучили и признаете как единственно верный. Сторонников этого варианта вы видимо и имеете ввиду когда говорите про

тех кто разбирается в предмете, естественно.

Это похоже на какую то секту, извините.

Хорошо, понятно.

Это похоже на какую то секту

Секта тех, кто наблюдал за историей Visual J++ и терок между Microsoft и Sun.

Нужно сказать, что расширять языки своими нестандартным конструкциями в 1990-е были модно. В C++ это делали и Microsoft, и Borland. Microsoft по привычке и на Java таким образом покусилась, но получила по зубам от Sun.

Microsoft по привычке и на Java таким образом покусилась, но получила по зубам от Sun.

Я только не понимаю почему это называется "получила по зубам", по мне так C# намного лучше чем Java.

И кстати после С и С++ все как то выглядит очень естественно на С#, вплоть до указателей в Unsafe моде. (хотя у меня, конечно, предвзятое мнение, я все также сижу в Вижал Студии)

У меня когда то был опыт работы на С++ через встроенный SQL с базами данных (библиотекой что ли это назвать-не знаю, скорее расширение компилятора), так LINQ это просто песня по сравнению с тем гемороем.

Я только не понимаю почему это называется "получила по зубам"

ЕМНИП, MS пыталась продвигать J++ как Java для Windows, акцентируясь именно на том, что это почти та же самая Java. А Sun запретила это делать. И выяснилось, что у MS нет безопасного и быстрого языка с GC для своей платформы вместо C++. Поэтому и потребовалось рожать C#.

Если бы Sun более лояльно отнеслась к нарушению максимы "write once, run everywhere", то история могла бы пойти по совсем другому сценарию.

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

J++ как Java для Windows

, а пришлось назвать С#.

Не велика потеря для истории, все таки, как мне кажется.

Боюсь ошибиться, но когда MS начала переделывать Java под себя, будущий автор C#, Андерс Хейлсберг, еще трудился в Borland над Delphi. А без него C# вряд ли бы случился. Может быть было что-то другое, но C# -- это детище Хейлсберга.

Автор комментария выше, говоря о том что C# появился как копия Java, в чем-то прав. Многие ключевые фишки: динамическая память, gc, jit, generics, exceptions, annotations, web-servlets - C# реализовывал сразу же как они появлялись в Java, некоторые вещи (базовые методы, иерархия исключений) тоже сделаны по аналогии с Java. В принципе нет ничего в этом плохого, всякая идея откуда-то заимствуется.

C#(назван в честь ноты, а не потому что C++++) — это в первую очередь Java++ с удачно взятым из Object Pascal разделением объектов на stack-allocated структуры и heap-allocated классы и кучей синтаксического сахара(а ещё с возможностью перегружать операторы и навешивать на всё аттрибуты), и с тем, что это первый "крупный" язык, где появился и синтаксис async/await, и "функциональщина в императивном языке", откуда это и разлетелось по другим языкам.
C++ в C# же почти нету. Вместо темплейтов дженерики, нет богопротивного множественного наследования классов, и вместо эрзац-интерфейсов в виде классов без реализации — полноценные интерфейсы, вместо RAII и ручного управления памятью — mark-and-sweep GC.(для контроля за другими ресурсами используется using и интерфейс IDisposable)
В C#, конечно, есть возможность лазить в память через сырые указатели, у которых ещё есть и арифметика указателей(а с .NET 6 — её вручную аллоцировать через malloc/free), но это используется в очень исключительных случаях, и спрятано за блоками unsafe(как в прямом конкуренте C++ — Rust, хоть и Rust вышел в релиз на почти полтора десятка лет позже C#).

C# назван в честь ноты
Тогда и пишите правильно: с♯.

добнее написать этот кусок

Таки а что вам мешает?

Array - массив строк по 70 символов. Каждая строка структурирована -
первые 6 символов CUS, потом три символа CLC, 6 символов CPNC, 35
символов CUN и 20 символов CRF. Кроме того, CUS + CLC рассматривается
как ключ (Key), остальное - как связанные с ним данные (Data).

Вот тут совершенно непонял что написано. Вы хотите какой-то набор байт в структуру десериализовать? Если да, то хоть renitepret_cast используйте,хоть union type, хоть flatbuffers или ещё какую-либу - не понимаю проблемы.

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

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

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

Забитых в коде? О_о

Ну почему бы не выложить на github const unsigned long perevod_na_sberbank_ivanov_a_a = 1234'5678'...;

Когда объект будет уничтожен или не будет более использоваться после выполнения выражения, целесообразнее переместить (move) ресурс, а не копировать его.

Ничего не понял. Объект и ресурс - это одно и тоже, или разные вещи? Зачем перемещать ресурс, если он будет уничтожен?

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


Но написано и правда странно.

Тогда надо было написать как-то так:

Когда объект, имеющий указатель на ресурс, будет уничтожен или не будет более использоваться после выполнения выражения, сам ресурс целесообразнее переместить (move), а не копировать его.

Я не понимаю, почему в обучающих примерах постоянно пляшет инициализация - то через ( ), то через { }. Как человеку, который хочет разобраться, понять, что правильно?

Разобраться что в С++ правильно? Ха-ха! Желаю удачи!

UFO just landed and posted this here

в новом коде предпочитайте всегда использовать {} когда возможно. Из плюсов:

  • сразу видно, что это не вызов функции, а инициализация

  • более строгая проверка типов

Из минусов:

  • более строгая проверка типов

  • весь старый код переписывать никто не будет

В обучающих примерах для начинающих не должно быть инициализации через () - разве что где-нибудь вначале сделать дисклеймер, что старый код может использовать () из исторических причин, чтобы знать как читать код. Равно как в обущающих примерах не должно быть printf или auto_ptr, хотя может быть глава/параграф о них.

@rikki_tikki, обновите пожалуйста здесь, () неуместны:

std::tuple user2("M", "Chy", 25);

и здесь:

T tmp(std::move(a));

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

Не все так просто, к сожалению: https://wandbox.org/permlink/DQ1f2ypC39vHfzeu

увы, но да, это 2 совсем разных конструктора.

неявный initializer_list в таком синтаксисе и к тому же везде-где-только-можно в stl в таком виде ИМХО был ошибкой в С++11

Типа того.

Поэтому я бы лично давал бы рекомендации чуть осторожнее: если нужно инициализировать объект типа T, то следует посмотреть на список его конструкторов и:

  • если там нет конструкторов с initializer_list или же все конструкторы принимают initializer_list, то использовать {};

  • если там есть и конструкторы с initializer_list, и конструкторы без оных (как в случае std::vector, std::basic_string), то выбирать между {} и () так, чтобы случайно не вызвать конструктор с initializer_list когда это не нужно.

Правда, здесь еще нужно сделать ремарку касательно aggregate initialization для структур, у которых вообще нет явно описанных конструкторов... В общем, не так все просто, как хотелось бы :(

следует посмотреть на список его конструкторов

Вы предлагаете написать код, который в будущем пойдёт по одному месту из-за того что в тип добавили конструктор, причём код начнёт молча делать что-то другое. Звучит как раскладывание граблей. Зачем?

Зачем?

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

ИМХО, конструктор с initializer_list нужен классам-контейнерам или мимикрирующим под них. Соответственно, там подобные конструкторы должны появляться сразу как часть интерфейса.

Тут не всё так просто. {} можно перепутать с aggregate initialization и initializer list. Это даже вызывает проблемы когда раскрываются некоторые шаблоны. Есть мнение что для конструкторов должно быть строго ().

Есть мнение что для конструкторов должно быть строго ().

Тогда, к сожалению, теряются некоторые проверки, которые делает компилятор при инициализации примитивных типов:

char c{129};

Внутри шаблона будет непонятно конструктор это или нет:

template<typename T>
void do_something_special_for_me() {
  T c{129};
  ...
}
...
do_something_special_for_me<char>();

Да. Поэтому я упомянул про проблемы в шаблонах. Хотя там это можно порешать определив категорию приехавшего типа.

auto add(X x, Y y) -> decltype(x + y)

наверное, не самый удачный пример, потому что начиная с C++14 можно просто написать

auto add(X x, Y y)

Пишу на плюсах с 1998. В последние годы всегда в С++ стараюсь всё это богомерзие подпрятать под нормальные, чистенькие собственные классы. Невозможно на это "развитие" без кровавых слёз смотреть.

Для запуска потоков тоже свои классы пишете? И контейнеры вроде std::multimap тоже свои?

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

И дальше — хуже. Все эти basic_string::resize_and_overwrite() и прочее. Страшно. У меня был коллега, любил длиннющие имена методам давать и валить их кучей. Смеялись, вместе с ним причём, он над собой тоже. А тут такое.

Хотелось бы конкретный пример того, что считаете нечитаемым, а то непонятно, о чем речь.

Ranges с боку пришили: replace_with_range, append_range, to_underlying remove_cv_t, _v/_t -конвенция. Из более раннего, — типы эти а-ля &&, move-семантика и схлопывание их. В принципе, в отвращении к попыткам понять, зачем же так извращённо реализовывать в языке понятную в общем-то идею, я и стал отходить от "продвинутого С++" к пуристскому. Из более нового, например: std::unreachable() — a utility function (from the header) that allows programmers to tell the compiler that a certain execution path cannot be reached: зачем говорить что-то компилятору через функции библиотеки, внутри настолько типо-магические, что хочется перекреститься? Зачем расширять язык через std::initializer_list? Нельзя было ещё более длинное название придумать?

 типы эти а-ля &&, move-семантика и схлопывание их

И как без этого пользоваться хотя бы std::string без лишнего оверхеда?

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

Мув-семантика-то вам чем не угодила?

А попробуйте её коротко объснить, как она в С++ изготовлена, но при этом реалистично, не сваливаясь в популизм? Я бы с удовольствием почитал. Я помню читал Майерса по Modern C++ лет 8 назад, и понял, что мы приплыли. И он тоже. Он после этой книги "вышел на пенсию по С++". Вся книга была попыткой объяснить move semantic в основном, вернее именно то как оно было сделано, и целая книга с этой задачей не справилась. Утонули в амперсандах. Более нарочитой машины Руба Голберга в жизни не встречал, чем развитие С++ с 2011.

Многое из того, что Вы упомянули, я не использую. Про initializer_list - им можно по-разному пользоваться. Например, достаточно бывает так: avc = {}

Но это не значит, что в том что вы используете этого нет. И это накладывает.

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

Что0то мне аж интересно стало, под какой класс-обёртку вы этот ненавистный std::unreachable() спрятали...

Как было где-то сказано, "Even as a C++ programmer with too much spare time, it has become obvious that learning and using all of C++ is beyond impractical". У меня вызывает стойкое отторжение очередные порождения комитета, которые сами не очень понимают уже давно исходную цель языка; это творчество похоже лишь на мыслеформы, приправленные бесплатным комитетским пивом. Забываем важнейшие вещи, зато успеваем пропихнуть бесселевы функции, очень "полезно".

На мой взгляд, внутри нормального базового языка типа С++98 вырос настоящий урод.

Зачем прямо use all of c++? Я пользуюсь только тем, что нужно для решения конкретной задачи.

Вот честно - не понял от слова "совсем", о чем Вы.

Вам приятно проваливаться в исходники стандартной библиотеки или ходить там по стеку? Мне нет.

Не могу понять, зачем бы мне это было нужно.

Для сокращения имён при использовании в конкретном исходнике есть using. Стандартное имя в библиотеке должно быть максимально понятным.

Вы так говорите как будто я этого не знаю :)
Вам using помогает при отладке? Нравится вместо string видеть в логе компилятора или не дай бог линкера простыню из std::basic_string со всеми шаблонными параметрами?

Это одна из причин для ввода собственных, очень конкретных и часто даже нешаблонных типов-обёрток над этим безобразием. Всё сразу лучше читается, отлаживается и т.д.

Ещё одна фича explicit - его управляемость. explicit(bool) включает-выключает явное приведение типов.

template<class T>
class StonglyTyped {
public:
  template<class U>
  explicit( ! is_implicit_construction_allowed_v<T,U> )  // напишите предикат сами
  StronglyTyped(U);

  template<class V>
  explicit( ! is_implicit_conversion_allowed_v<T,V> )  // напишите предикат сами
  operator V() const;
};

Да прибудет с вами C++!

Куда и когда именно прибудет с нами С++?
Да пребудет с вами грамотность.

std::pair<std::string, int> user = {"M", 25}; // раньше
std::pair user = {"M", 25};                   // C++17

А разве во втором случае мы не получим std::pair<const char*, int>? Это ж как бы не то же самое.

Sign up to leave a comment.