Comments 422
Еще конечно хочется полностью новый STL. Текущая стандартная библиотека мне видится тупиком. Взять хотя бы
std::string
— на CppNow, Sean Parent рассказывал что у них там 4 реализации строк в коде. А все почему? Потому что обычный API этой строки убогий, а выкинуть и сказать «мы накосячили, давайте заново» никто не готов.Еще конечно хочется полностью новый STL
В Оулу решено было забронировать все namespace stdцифры для будущего использования стандартной библиотекой. Тоесть когда-нибудь и правда может появиться std2::. У людей в комитете уже сейчас есть идеи для новой версии стандартной библиотеки. Но вот появится она наврядли в ближайшие 10 лет.
30 лет «ходим на костылях», а комитет грезит о дальнем космосе
запилить строки, синхронизацию, работу с файлами и сетью (+ асинки) — закроет 95% потребностей программистов
одну либку возмешь, она зависит от boost::asio, другая с необходимыми вещами, что нет в первой — от libevent… кто-то еще притянул ProtoBuf, а еще один решил, что для разных клиентов будет удобнее Thrift(с их сервисами и транспортами)… в итоге в приложении 3-4 либки работы с сетью (неплохо бы еще ZeroMQ с подписками и RabbitMQ для message-queue), 2-3 вида строк и 5-6 реализаций мьютексов и умных указателей… рукалицо/больпечаль
что касается новой STL из-за строк, то это ты погорячился: достаточно одобрить extension methods и впилить icu-либку, уже будет счастье:
string toLower( string_view sv ) {… }
auto str = string(«Hello WORLD!»).toLower();
offtopic: конечно в узкоспециализированных вещах С++ замены нет, но НЕ для «железного» уровня рассматриваю C++ как низкоуровневую(нативную, шуструю) реализацию некоторых вещей для более удобной/человеческой(созданной для людей) среды, что-то вроде С++ — это интринсики для C#/Java (еще бы их interop был гладкий… мечты)
Можно ссылки? Я волшебное слово знаю — ДАЙ! пожалуйста!
Берешь графический движок, в нем Vector3, берешь физически движок в нет Point3, берешь звуковой в нем float3, берешь либу с поиском пути в ней Float3 и сидишь пишешь бесконечные конвертации точек и матриц из одной либы в другую, со строками такая же бида.
Cargo. Просто Cargo. И все эти 3 либы будут использовать один набор типов из крейта 3d-geometry.
Упс, мы же про С++. Нет, не судьба.
Строго говоря, Vector и Point — это разные типы данных (даже если одинаковые структуры данных).
по-code-point-но вы имеете в виду?
Но это мысли — интересно в какие слова они облекутся.
PS просто не всегда нужно умляуты и прочие радости учитывать при обходе строки (например, если ищешь цифры — то они никакого значения не имеют).
PS просто не всегда нужно умляуты и прочие радости учитывать при обходе строки (например, если ищешь цифры — то они никакого значения не имеют).Если ищешь цифры (парсишь XML или там JSON), то достаточно работать с байтами если у тебя UTF-8.
PS под возможной кодировкой я подразумеваю, что для хранения данных в типе строки кодировка (по уму) должна быть одна, но её можно выбирать из разных кодировок, в то же время я знаю две универсальные кодировки — UTF-8 и UTF-16.
Тут всё зависит от задачи. Если задача — просто распарсить JSON, то работа со строкой будет осуществляться как с обычным массивом с использованием небольшого числа строковых функций. В случае программирования микроконтроллера полноценная поддержка юникода тоже явно будет лишней.
Я бы вообще не изобретал велосипед, а пользовался исключительно средствами операционной системы. В STL включил бы только врапперы для их вызова. Для основных задач обработки строк этого достаточно. Ну а что касается поддержки экзотических случаев — не стоит ими засорять стандартную библиотеку.
Мне кажется, что все-таки в МК используют либо свой диалект Си, либо некое подмножество современного С++.
Но то что язык идет в тупик — это факт.
(С++ реально крутой, но каждая итерация нового стандарта делает его все монструознее и монструознее)
Языку очень-очень нужны модули и что-то вроде пакетного менеджера, вроде того что у Rust — лучше сподвигнуть людей писать больше лёгких в использовании библиотек, чем засовывать в комплект ещё больше батареек.«Батарейки в комплекте» — штука тоже хорошая, но тут у C++ тоже огромная засада: так как каждый тянет одеяло на себя, то в результате в стандарте — очень странная комбинация из вещей, которые мало кому нужны (но зато против которых никто не возражал) и полнейшее отсуствие вещей, которые всем нужны, но про которые каждый хочет чего-то своего.
Простейший пример: ни запустить подпроцесс, ни разобрать параметры, пришедшие в
main
(с выделеним обязательных/необязательных параметров, etc) средствами стандартной бибилиотки нельзя. Разработчикам систем для микроконтроллеров это, в общем-то и не нужно — но зато нужно всем остальным!std::system запускает подпроцесс.Лучше бы он его не запускал. Количество дыр в безопасности, которые образовались из-за того, что в стандарте есть только std::system — не счесть.
Но да, запускает, тут вы правы.
А разбирать параметры, пришедшие в main, можно далеко не единственным способом (как минимум, синтаксис параметров в винде и линуксах разный).С одной стороны — вы правы: для создания переносимых программ какой-нибудь getopt не годится.
С другой — это значит что сотни тысяч программистов пишут сотни тысяч велосипедов при написании простейших утилит (потому что требование «использовать только стандартуную библиотеку» у заказчиков встречается частенько). Зачем? Ради чего вся эта бурная деятельность?
Я прекрасно понимаю почему члены комитета не могут решить эту проблему — но это не меняет того факта, что конечный результат (огромный и очень сложный язык, который, тем не менее «из коробки» не умеет «элементарных вещей») — очень неприятен.
Никогда не пробовали объединять код, написанный даже не на основе разных библиотек, а на основе всего-навсего всем известного boot'а? Когда один компонент хочет версию 1.20, а другой 1.50… подружить их бывает ох как непросто.
Вопрос в том, что умляуты и прочие код-поинты, не учитывающиеся как символ для некоторого класса задач не имеют значения.Вот я и прошу пример «класса задач». Пока всё, что я видел распадается на два класса: либо оно нормально работает с UTF-8 с побайтовой адресацией, либо адресация по кодпоинтам задачу не решает, а просто загоняет проблему вглубь. Как я написал выше: «шоб с русским работало, а все немцы и турки идут в жопу». Я, в общем, понимаю почему такой подход международный коммитет по стандартизации не очень хочет поддерживать.
PS под возможной кодировкой я подразумеваю, что для хранения данных в типе строки кодировка (по уму) должна быть одна, но её можно выбирать из разных кодировок, в то же время я знаю две универсальные кодировки — UTF-8 и UTF-16.И опять-таки дихотомия неправильная. Есть два класса кодировок:
1. UTF-8 — кодировка с которой просто работать и расширять языки не нужно
2. Всё остальные кодировки — работать с которыми сложно и потому расширять язык тоже не нужно
UTF-16 относится ко второму классу. UCS-2 (из-за которой в некоторых языках и операционных системах и появилось угробище под названием UTF-16) — да, была проста и красива. Хотя платила за это наличием своих собственных недостатков. UTF-16, увы, имеет все недостатки UTF-8 (один кодпоинт и один элемент строки — разные вещи), но также унаследовала все недостатки USC-2 (например бывает UTF-16LE и UTF-16BE, а это значит что вам нужен BOM и вам нужно знать есть у вас BOM или нет и т.д. и т.п.)
Использовать её нужно только там где есть внешний API, требующий её использования — но наличие внешнего API автоматически обозначает что есть и процедуры для работы с этим творением человеческого разума, а значит иметь API в языке — уже не нужно.
Вообще-то, в UTF-8 тоже есть BOM...
Я так понимаю, он выступает в качестве сигнатуры кодировки. Ссылку не найду.
Но в том же C# любой текстовый файл, записанный в кодировке UTF-8 по умолчанию будет иметь BOM.
Именно строк как набора символов, а не байт.Можете привести хоть одну задачу, где это реально нужно?
Я знаю много задач где подобный подход породит проблемы (то есть: оно будет работать в простых случаях, но рассыпется нафиг в сложных) и не знаю ни одной практической задачи, где бы это помогало.
P.S. Так, навскидку: не забываем что upcase("ß") == «SS», что upcase(«i») ==«I» у большинства народов, но турков upcase(«i») == "İ" и т.д. и т.п.
Вообще-то тут обсуждались строки в UTF-8, а не в произвольной кодировке.
operator[]
для ISO-2022, а я посмеюсь. Только про эскейп-символы и связанные с ними радости не забудьте, пожалуйста.На вопрос «зачем» ответа так и не последовало, хотя, я на 99% уверен в своей версии: «нам с русским языком работать не удобно, а о немцах, турках и японцах мы думать не хотим — пусть идут куда им захочется… хотят на три буквы, хотят — на пять или десять».
Как вы понимаете такая мотивировка в международном комитете по стандартизации вряд ли получит движение.
По хорошему, строки и символы должны быть отделены от массивов, байтов и кодировок. И уже разработчик будет решать, что ему использовать. Нужны что-то более сложное — вот тебе полноценные строки, но за них придется платить памятью и скоростью. Хотя юникод и поддержка многих языков в любом случае не будет бесплатной.
По хорошему, строки и символы должны быть отделены от массивов, байтов и кодировок.А кто их отделять будет? Вопрос не праздный: Python3 решил пойти по этому пути, что привело к тому, что пользоваться этим ужасом просто невозможно.
И уже разработчик будет решать, что ему использовать.Если бы всё было так просто. Проблема в том, что переход между «потоком байт» (ну, например, именем файла в Linux'е) и «текстом» (ну, например, сообщением «файл «...» имеет размер «...» байт») вовсе не так очевиден как хочется и кажется.
Нужны что-то более сложное — вот тебе полноценные строки, но за них придется платить памятью и скоростью.А что будет если вы отнесёте что-то не в ту категорию? Программисты очень часто не задумывась, относят что-то не в ту категорию — и если у вас есть два жёстко разделённых мира, то исправление этой ошибки — очень дорого. Если у вас и то и другое — строки (подход C++, Go, Python2 и множества других языков) — то жить становится гораздо легче.
Стандартизация библиотеки для работы с текстом — дело хорошее, только, ради бога, не засовывайте это прямо в язык.
Это то с чего началась данная ветка: дайте, типа, нам посимвольный доступ в UTF-8 строку. На вопрос «зачем» ответа так и не последовало, хотя, я на 99% уверен в своей версии: «нам с русским языком работать не удобно, а о немцах, турках и японцах мы думать не хотим — пусть идут куда им захочется… хотят на три буквы, хотят — на пять или десять».
А японцы, которых Вы упомянули, по-Вашему, что UTF-8 — не используют?!
Это необходимо всем у кого алфавит не на латинице, то есть как минимум 3 миллиардам человек = китайцы + индийцы + японцы + корейцы + таиландцы + пакистанцы + бангладешцы + арабы + и так далее… То есть даже без России — это половина человечества, если не больше, так как в Африке существуют алфавиты тоже основанные не на латинице.
Я последние 3 года разрабатываю старый Web-проект с бэкендом на плюсах (+ свой проект тоже на плюсах). Ни разу не было реальной необходимости в посимвольном доступе к UTF-8 строкам.
В тех же случаях, когда сложная работа с UTF-8 действительно нужна, лучше просто взять специальную библиотеку, а не желать этого в стандарте языка.
Это примерно как желать 3D движок в стандарте C++. Но что-то я не вижу желающих интегрировать в стандарт, например, OGRE.
UTF-8 — это просто способ кодирования 31-битных значений в виде последовательности 8-битных байт. Он никак не привязан к смысловому содержанию этих значений.
Для подавляющего большинства практических задач обработки строк посимвольный доступ (=преобразование UTF8 в UCS2/UCS4 на лету) не нужен — можно работать напрямую с UTF8 представлением строки.
Если всё-таки нужен произвольный посимвольный доступ, то будет гораздо эффективнее преобразовать строку в массив 2-байтных или 4-байтных значений, а после работы преобразовать обратно. А если ещё хочется и менять значения символов на месте, то как вы себе это представляете для UTF8?
Но вот мне, например, недавно нужно было искать в тексте кавычки не только обычные, но и “/”, «/», а также символ троеточия.
Если вы пишете текстовый редактор, вам скорее всего потребуется разбивать текст на слова, причём разделителями, как вы понимаете, могут служить не только ASCII-пробелы.А ещё их может не быть — совсем. Как в Китайском и Японском, к примеру. И вам всё равно нужно знать на каком языке текст и нужна отдельная процедура, которая этим занимается.
Или если вы пишете компилятор для языка программирования, поддерживающего юникодные спецсимволы (ну как в Mathematica и Haskell), то вам тоже понадобится посимвольный разбор.Зачем? Если вам достаточно «халтурной» реализации — посимвольной обработки хватит. А если вы хотите полноценную поддержку, то тут всё сложнее. Нужно как-то обрабатывать LTR, возможно придётся как-то озаботиться сравнением Hangul Syllables и Hangul Jamo и прочее.
Не понял, чего нет в китайском и японском? Пробелов?Угу. Для того, чтобы понять где можно переносить строку нужно использовать достаточно сложный алгоритм, содержащий, среди прочего, ещё и словарь иероглифов…
Ну в этой-то отдельной процедуре настоящие юникодные строки вам будут нужны, так ведь?Скорее нет, чем да. Оперировать с отдельными символами вам там нужно будет очень редко, вполне достаточно требования что на входе — корректная UTF-8 строка. Вот это — да, нужно чётко отделять уровни где у вас std::string — это ещё «поток байт» и уровни где это уже текст (==корректный UTF-8).
Со строками например такая проблема: у строки есть и size()
и length()
. Вот представьте вы начинающий разработчик, хотите понять что это за функции такие? Интуиция как бы подсказывает, что length
= количество буков (символов), а size
= размер "сколько вешать в байтах". Но не тут-то было. К тому же, в упор непонятно, почему это length()
— функция, а не свойство строки. Но это уже другая история.
Про UTF-8 я вообще помалчиваю. Мне бы хоть ASCII но с вменяемым синтаксисом. Где split(), to_lower()
, и прочие очевидные вещи? Почему я использую Boost на каждый чих? Ведь Boost — это глобальные функции, а это полная отсутствие discoverability, то есть, есть у вас IDE, нет ее, не важно, т.к. комплишн по всем глобальным символам еще-не-заимпортированных заголовков не сделать в принципе.
Экстеншн-функции — хорошая, проверенная в C# тема, но с ней тоже как-то не торопятся. А даже если сделают, мы что, будем стандартизировать эти "патчи" стандартным типам? Или каждый тихо в гараже запилит свой split()
и его будет шипить как сорцы?
Вообщем, проблем много.
30 лет не могут сделать нормальные строчки — поиск, замена, lower/upper/locale
30 лет «ходим на костылях», а комитет грезит о дальнем космосе
запилить строки, синхронизацию, работу с файлами и сетью (+ асинки) — закроет 95% потребностей программистов
Точно вместо удовлетворения необходимостей, комитет занимается тем что блаженно чешет своё ЧСВ. :(
PS создайте кто-нибудь петицию на Change.org чтобы до этих зажравшихся козлов в комитете наконец дошло!
STL ужасен до невозможности после опыта работы с более новыми языками с нормальными библиотеками. Больше всего бесит широкое использование беззнаковых типов и смешение знаковых/беззнаковых. Затем — ужасный iostream с нелогичными перегрузками и форматированием. В качестве прикола пытался написать аналог .NET String на C++ с SubString (с указателями), особенно String.Format с variadic templates. Даже работало и было на порядок удобнее printf и тем более cout.
И я очень надеюсь, что с введением модулей будет полностью переработана STL.
Я вот думаю, так под шумок модулей можно и STL переписать. Но это скорее мечты.
auto_ptr
до сих пор поддерживается, а не COW-строки в GCC начали разрабатываться за много лет до появления стандарта (оказалось что COW-строки на многопроцессорных системах сильно медленнее, а экономия памяти редко когда стреляет).
Если вы не обнаружилось что из распространённых компиляторов только один использует COW-строки, да и тот планирует «при удобном случае» от них отказаться — фиг бы чего вышло. Да и то: это изменение старые программы не поломало, максимум — изменило потребление памяти.
Реализацию, наверное, не template-функций убрать из библотеки будет сложно, но никто в приципе не мешает старым программам требовать старую stdlib.
А насчёт COW — это я имел ввиду то, что бинарную совместимость тоже иногда ломают.
А насчёт COW — это я имел ввиду то, что бинарную совместимость тоже иногда ломают.А тут как раз очень забавно. У Microsoft'а она вообще ломается каждую версию, а в Linux'е введение не-COW строк, строго говоря, бинарную совместимость не сломало. Тeперь в стандартной библиотеке есть std::string и std::__cxx11:string — и, в теории, ничто не мешает библиотекам и программам продолжать поддерживать и использовать оба вида строк…
Но вообще вся моя идея была в том что функдамент миграции на новые бинарные и не совсем реализации заложен и вполне себе используется, так что я не очень удивлюсь если когда-то будут совсем выкидывать старые куски, требуя использовать старые компиляторы/библиотеки если что не так.
А сейчас нет чего-нибудь что требует страый gcc для сборки?
Строго говоря как раз таки сломало.
Если разные модули будут использовать в своих API stl строки, и будут собраны в режиме С++11, но один компилятором GCC-4.*, а второй — GCC-5 и выше, то они будут ABI не совместимы.
Если разные модули будут использовать в своих API stl строки, и будут собраны в режиме С++11, но один компилятором GCC-4.*, а второй — GCC-5 и выше, то они будут ABI не совместимы.Зависит от того — как именно вы используете GCC-5. Вы можете им собрать свою библиотеку с
std::string
, с std::__cxx11:string
и даже с обоими вариантами одновременно.В общем случае нет, не зависит.
Собрать то свою библиотеку я действительно могу как угодно, но если какая-либо библиотека уже собрана (например, компонент без исходников) пусть в gcc-4.9, а в дистрибутиве линукса установлен gcc-5+ (с новым ABI по умолчанию), то эта библиотека будет не совместима с поставляемыми в дистрибутиве другими библиотеками (они то используют std::__cxx11:string вместо std::string).
По Вашей ссылке об этой проблеме тоже написано:
> If the third-party library cannot be rebuilt with the new ABI then you will need to recompile your code with the old ABI.
А «your code» вполне может быть и энное количество поставляемых в системе библиотек.
Какая же это совместимость ABI, если требуется «recompile your code with the old ABI»?
В Clang модули реализованы уже несколько лет (правда не совсем тот вариант что пошёл в стандарт).
А где можно почитать «почти готовый» стандарт про них? Интересно, как он выглядит на данный момент.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0142r0.pdf
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0143r2.pdf
Некоторые мысли по статье
1. Как «if (init; condition)» будет взаимодействовать с объявлением переменных внутри круглых скобок if, т.е. «if(int x = foo())»?
Кстати, сама по себе форма уже очень близка вот к этому https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html, т.е. к тому чтобы сделать стейтменты выражениями, как в Scala или Nemerle.
2. Structured bindings… получается очень забавно — в С++ со всех сторон подошли к прямой реализации кортежей средствами языка, но видимо из-за «legacy» синтаксических особенностей так и не сделают. А так почти все есть — и списки инициализации, и pair/tuple, и вот теперь structured bindings…
1. Как «if (init; condition)» будет взаимодействовать с объявлением переменных внутри круглых скобок if, т.е. «if(int x = foo())»?
Если нет `;` в `if`, то считается что в круглых скобках condition. Другими словами — работать будет так же, как сейчас.
2. Structured bindings… получается очень забавно — в С++ со всех сторон подошли к прямой реализации кортежей средствами языка, но видимо из-за «legacy» синтаксических особенностей так и не сделают. А так почти все есть — и списки инициализации, и pair/tuple, и вот теперь structured bindings…
Одно из предложений, которые обсуждали на собрании, как раз было что-то похожее на «кортежы средствами языка». Но это был скорее исследовательский доклад, и решено было пока продолжить исследования и посмотреть что получится.
Интересны пределы возможностей, так как реально это очень похоже на возможность использования цепочки стейтментов в выражении.
Кстати, «if constexpr» это небось из D «static if» таки протащили?
ну фактически static if заменяет некую функциональность как шаблонов так и макросов, т.е. может быть использован для условной компиляции и метапрограммирования.
if (int x = foo(); double y = bar());
А также вместо инит стэймента может быть экспрешн стэйтмент:
if (do_something(); int x = foo());
Кстати, кланг уже поддерживает эту фичу (спасибо мне и Ричарду Смиту :)
Просто комитет не занят стандартизацией только библиотек Boost.
filesystem вообще-то есть.
Даже pragma once(или ее аналог) не могут в язык внести.
Может, потому что эту «фичу» невозможно корректно имплементировать? Уже сто раз оговорено, что pragma once не должна использоваться в нормальном коде.
Можно ещё раз для тех, кто всё пропустил? Хотя бы ссылку. Потому что у нас pragma once используется и не вызывает никаких проблем, а кодовая база собирается разными компиляторами.
У систем сборки (того же make, к примеру) есть точно такая же проблема — они не могут различать артефакты (цели) по символическим ссылкам в общем случае.
Означает ли это, что системы сборки на основе артефактов не следует использовать? Нет, не означает. Это означает что всевозможные ссылки надо использовать осторожно.
И с pragma once то же самое.
И с pragma once то же самое.Нет, не то же самое. Систем сборки не страдающих от указанных вами проблем я не видел (для языков без модулей, в общем-то, и альтернатив не придумать), а для
pragma once
альтернатива есть, описана в стандарте и работает ничуть не хуже.Систем сборки не страдающих от указанных вами проблем я не видел
Хм, вроде как SCons по умолчанию использует md5 по содержимому файла для определения его уникальности. Вроде это должно помочь при сложностях со ссылками.
Хм, вроде как SCons по умолчанию использует md5 по содержимому файла для определения его уникальности.Если бы SCons этим занимался, то он бы обрабатывал мало-мальски сложные проекты невменяемо долго. MD5 Scons использует, но уже после проверок на изменение даты. Если файл изменится, а дата модификации и размер останутся неизменными — SCons ничего не заметит…
Всё просто: посмотреть на метаинформацию можно даже если у вас в проекте сотни тысяч файлов. А делать что-то другое — слишком накладно если у вас исходников достаточно много.
Если бы SCons этим занимался, то он бы обрабатывал мало-мальски сложные проекты невменяемо долго
Это очень похоже на мое о нем впечатление)
Смотрю в доках:
By default, SCons keeps track of whether a file has changed based on an MD5 checksum of the file's contents, not the file's modification time.
И судя по тем же докам, проверка времени изменения перед проверкой хешей опциональна и требует явного включения — Decider('MD5-timestamp')
.
Спасибо.
Из любопытства заглянул в исходники Clang: там и правда инклюд гарды используются. Тем не менее, все популярные компиляторы эту прагму поддерживают ведь. И я как-то не встречал рекомендаций от неё отказываться. Если она такая проблемная и это "общепризнанно", то могли бы хотя бы предупреждение выдавать.
https://ru.wikipedia.org/wiki/Pragma_once
В курсе, но результат как раз укрепляет меня во мнении, что проблемы с этой прагмой редкость.
Справедливости ради, у решения "лишённого недостатков pragma once" есть свои недостатки, пусть и незначительные. Например, необходимость вручную следить за уникальностью имён, хотя заранее соглашусь, что это не особо сложно.
Ну нет, вот за этим, как правило, как раз следить не надо (если не брать отдельные хитрые моменты): при создании одноимённого класса/функции компилятор честно выдаст ошибку.
Повторюсь — я не считаю, что проблема уникальности именования гардов сколь-нибудь остро стоит. Но лично мне всегда приятно когда за разными мелочами следит компилятор, а не самому требуется внимательность проявлять. По крайней мере, если автоматика не приносит свои особенности/недостатки.
при создании одноимённого класса/функции компилятор честно выдаст ошибку.При использовании одномённых гардов — тоже. Теоретически можно придумать код, который не вызоват ошибку при коллизии, а приведёт к неправильной работе — ну так и с именами функций такая фигня бывает.
Но лично мне всегда приятно когда за разными мелочами следит компилятор, а не самому требуется внимательность проявлять.presubmit-скрипт решает это проблему…
#ifndef MY_HEADER_#FILEHASH#
#define MY_HEADER_#FILEHASH#
…
#endif?
P.S. Когда может оказаться полезным трактовать разные файлы как одинаковые? Когда это — разные версии одного и того же файла.
P.P.S. Я не говорю что существующее решение — идеально. Но так как все «простые решения» имеют свои подводные камни, то лучше не пытаться забить очередной костыль (который всего лишь заменит одну проблему другой), а всё-таки подумать на тему модулей. Ну не создают нормальные люди новые файлы так часто, чтобы эти три строчки были проблемой, не создают!
inline для переменных — если в разных единицах трансляции присутствует переменная с внешней линковкой с одним и тем же именем, то оставить и использовать только одну переменную (без inline будет ошибка линковки);
Извините, но это капитальнейшая подстава и здоровенная пуха, которая теперь будет отстреливать ноги всем и по самую шею!
иногда CPP-файл создается просто для того, чтобы в нем определить переменные… уныло
std::string_view
std::array_view я так понимаю не будет?
Тут кстати интересная дуальность. Везде в STL у нас обобщенные алгоритмы которые берут begin()/end()
не важно откуда. А для строк почему-то свой string_view
хотя по логике вещей могли бы дать и array_view
и сказать "а дальше вы сами". Ведь по сути, пока нет поддержки юникода, строки и массивы — это одно и то же :)
Во-первых, у begin-end нельзя взять ни длину, ни подмассив указанного размера.
Во-вторых, при сшивании со старым кодом часто нужно передать пачку элементов непрерывным буфером. begin-end тоже пасуют.
По поводу же строк, у них есть ряд методов, которых нет у не-строковых срезов.
Ну и наконец, моё сугубо личное мнение, что сама по себе идея делать 2 итератора на диапазон, да ещё и требовать от них совместимости по интерфейсу с указателями, была отвратительной. Потому что реально в случае с RandomAccessIterator, например, у нас не 2 итератора, а слайс — убого скрытый под 2-мя итераторами. А теперь городим костыли в виде разнотипных итераторов. И, кстати, в stdlib до сих пор нет шаблонов для конструирования этих самых итераторов.
Ну, если на то пошло, давайте признаемся что в современном мире более рационально сделать некий интерфейс IEnumerable<T>
(привет C#) и поддержать ключевое слово yield
, которое дает возможность произвольно возвращать коллекцию даже тогда, когда это дерево и нужна рекурсия. В С++ сейчас писать итератор для дерева в рекурсивном стиле невозможно.
А потом можно передавать не begin()/end()
а просто сам объект. И все счастливы.
Примерно так и есть. Сделано, кстати, в одном языке на R, 4 буквы в названии. Ооочень удобно, даже без yield — когда итерирование по коллекции — 1 метод, а не как минимум 3-4.
Rust вообще-то. Почитайте на досуге, если интресно. Там, в частности, исправлены многие косяки С++.
Ну… евангелизм бывает навязчивым. (:
Если что, мне самому раст очень интересен, так что минусов не ставил, даже наоборот.
P.S. Swift меня не волнует от слова никак — точно так же, как не волнует, скажем, язык 1С (зачем мне язык намертво завязанный на чужую экосистему?), а на Rust — я поглядываю, но пока не решился ничего серьёзного на нём писать.
Бывает. Впрочем, я не евангелист — так, сочувствующий. Стараюсь если вставлять про Rust, то по теме обсуждения и понемногу. И давайте наверное закрывать эту ветку, а то совсем оффтоп получается.
Плюс, ограничение на тип элемента делается криво.
Плюс, функции, принимающие итератор, обязаны быть шаблонными — а принимающие срез конкретного типа — нет.
В общем, пользоваться можно — но срезы удобней.
template std::enable_if_t<std::is_same<std::random_access_iterator_tag, typename T::iterator_category>::value,void>
doSomething(T begin, T end) { /*...*/ }
Вот вам и ограничение «здесь принимается random access iterator».
Шаблонность в общем случае тоже необязательно — можно пользоваться auto функциями/лямбдами
std::variant
Они таки вставили null state. Не очень хорошо, честно говоря. Я так понимаю, это жертва для обеспечения exception safety. Хотя могли бы потребовать noexcept для деструкторов и move конструкторов.
Они таки вставили null state.
Да, он называется valueless_by_exception() и возникает крайне редко.
Хотя могли бы потребовать noexcept для деструкторов и move конструкторов.
Есть много вариантов как реализовать std::variant, у всех есть свои недостатки. Мне нравилась версия «требовать noexcept default constructor для хотябы одного типа». Но у этой версии большие проблемы с юзабилити, так что std::variant с valueless_by_exception() намного более юзабельный.
Не в курсе, к сожалению. Реализовывал свой велосипедик на эту тему, чтобы работал на GCC 4.9 и не аллоцировал память. В результате пришёл к двум ограничениям — noexcept destructor и noexcept move constructor. На их основе можно делать variant который или будет всегда в валидном состоянии, или уронит программу std::terminate. Оба ограничения как по мне вполне адекватные. Кидаться исключениями из деструктора нехорошо, а из мув-конструктора — просто глупо. Кстати, noexcept default ctor в таком случае не нужен.
Проблема в том, что в обычной жизни есть довольно много типов без noexcept move коонструктора. Некоторые стандартные контейнеры например.
Вот кстати не могу представить throwing move. Подкинете пример?
По-моему, аллокация памяти там сильно лишняя. Почему не могут просто отдать буфер? Ведь, по сути, recursive_wrapper почти то же самое, что и unique_ptr.
Если разрешить ему хранить nullptr и кидать исключение при разименоввывании (а его разименоввывание происходит внутри variant и скрыто от пользователя), то variant теряет never-empty guarantee и иногда начинает хранить пустой recursive_wrapper. В итоге получается нечто среднее между boost::variant и std::variant, чего всячески хочется избежать.
if (auto [x, y] = foo(); y == something) {… }
Как я понимаю, переменные, объявленные в секции init, существуют и внутри else тоже?
switch (auto [x, y] = foo(); y) {
case 1: x += 10; break;
// ....
default: x = 0;
}
switch (foo()) {
case [true, result]:
do_something(result);
break;
case [false, _]:
LOG(error)
switch (foo()) {
case [true, result]:
do_something(result);
break;
case [false, _]:
LOG(error) << "Shit happens!";
break;
}
Кстати, а плейсхолдеры добавили? Желательно что-нибудь минималистичное вроде того же "_", а не «std::structured_bindings::paceholders» 8)) И алиасы нподобие хаскеллевского «pair@(first, second)»? Понятно, что это сахарок, но без него таки не так сладко будет.
PS: прошу прощения, в прошлом комментарии что-то с форматированием стряслось…
Особенно неоднозначное отношение к string_view — как говорится если раньше было два способа написать функцию работающую со строками, то теперь их три. И каждый из них по-своему плохой.
Впитывая в себя куски из boost'а стандарт становится такой же беспорядочной помойкой — безумно распухшей коллекцией «прикольных фишечек».
string_view это как раз очень хорошо — но чертовски поздно. Как и array_view. Надеюсь, к 20-му году прикрутят. А должны были вкрутить ещё в самом начале, в крайнем случае в 11-й версии. Но не судьба.
Ну string_view как раз штука удобная и нужная, да и раньше ей пользоваться можно было, что я успешно и делал. А вот вещи типа "if (init; condition)" тоже настораживают. Может это, конечно, во мне консервативность говорит, но оно кажется далеко не самым частым частным случаем. Для сравнения for по диапазону из C++11 мне очень нравится, да и случай чуть ли не самый частый.
Мне кажется, что пора уже заканчивать с этим и нужно садиться за написание книг, где подробно описывать как теперь все это использовать и самое главное, что не использовать из старых практик, пока они сами не забыли как правильно.
Это, вообщем-то, основная проблема. Сейчас мы можем втянуть студентов в С++ только силой, т.к. добровольно никто на нем писать не будет — сядут за C# или Java, сделают свое приложение, начнут его продавать. А в C++ остались те кто, как я, слишком долго сидел в этой теме и имеет некоторые априорные знания о том как все было 10-15 лет назад, и как проэволюционировало.
Потому что стандарт распух до неприличия — а многих действительно важных вещей нет до сих пор. А многих, таких как простого менеджера зависимостей и сборки, не будет никогда.
Поэтому С++ со временем выдавят. На это уйдёт куча времени, но его место займут другие языки. Что забавно, С, думаю, останется сильно дольше — т.к. он гораздо проще.
Слушайте, я вот недавно в Сан Фран ходил на встречу SG14, и там один человек на полном серьезе предлагал встроить С++ package management в язык (!!!). Хотя на дворе 2016 год и идея внешнего описания зависимостей уже проработана и испробована в Cargo, NuGet и так далее. То есть даже в C++ Working Group есть люди которые далеки от понимания того, что творится в мире разработки.
Это, простите, капец. По-моему, это PL/1 v2 уже какой-то.
Хотя, с другой стороны, заставить всех использовать единый формат пакетов и проектов можно только жёстко впилив его в стандарт. Но этого не будет никогда — потому что MS, Google, GCC team будут вечно тянуть одеяло на себя.
/fanboy mode on
Хочу только одну вещь. Поддержку в Rust'е импорт C headers из коробки. После этого "два крестика" покатится cо всё нарастающей скоростью.
Хочу только одну вещь. Поддержку в Rust'е импорт C headers из коробки.
Мне кажется, усложнять язык для этого нет нужны, лучше доработать bindgen: https://github.com/crabtw/rust-bindgen#plugin
Для С++ я советую финансовый сектор в крупных городах — Лондон, Нью Йорк, Чикаго.
С чего вы взяли? Попасть куда угодно — реально если у вас есть навыки.
Но вообще, если вы найдёте работодателя-спонсора, то шансы достаточно велики, чтобы пробовать.
Будут-будут. Куча форумов исписано, поищите. Всякие гуглы-майкрософты-амазоны периодически устраивают глобальный чёс в поисках сотрудников и приезжают сами. Отправьте резюме, сходите на интервью. В первый раз не взяли, повторите через год. Другие компании проводят собеседование по скайпу/телефону. Третьи, хотят решение задач по имейл.
Если ваша цель переехать в другую страну, но пока что есть предложения только от, скажем, фейсбука, с которым у вас принципиальные разногласия, то можно перебится год-два и уже на месте найти то, что хочется.
А как они зависят? На обычных шаблонах, кажется, без проблем делается.
Таскать везде begin и end вместо одного range — неудобно.
Тот же string view — это же, по сути, range, но почему-то только для строк?
template<class InputIterator, class Predicate>
bool all_of(InputIterator first, InputIterator last, Predicate pred);
С Ranges он будет выглядеть вот так:
template<InputIterator I, Sentinel<I> S, class Proj = identity,
IndirectCallablePredicate<projected<I, Proj>> Pred>
bool all_of(I first, S last, Pred pred, Proj proj = Proj{});
template<InputRange Rng, class Proj = identity,
IndirectCallablePredicate<projected<iterator_t<Rng>, Proj>> Pred>
bool all_of(Rng&& rng, Pred pred, Proj proj = Proj{});
Оценить масштаб изменений можно почитав proposal: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf
А это как-то мешает сейчас ввести ranges как простой шаблон с begin и end, а потом (если это потом настанет) уже расширить его концептами?
Переделок будет даже меньше, чем делают сейчас, например, с параллельными алгоритмами.
Да, есть хорошие и удобные фичи. Но дофига нужного до сих пор нет!
Навскидку:
1) нормальные, черт возьми, строки!
2) модули
3) полиморфные лямбды
4) xml, json и прочая сериализация/десериализация из коробки
5) в мире существует сеть, и пора бы уже о ней узнать!
6) ну и еще кучу всего, ночью уже сложно вспоминать…
Что касается строк, есть интересная статья на эту тему: https://mortoray.com/2013/11/27/the-string-type-is-broken/
2) ну все ждут и все хотят, но это такая глобальная тема, что комитет не хочет сделать какашку, от которой следующие 30-50 лет люди будут страдать.
3) а сейчас что не так
4) дайте рефлексию, и все это будет не нужно. А черновиков по рефлексии несколько уже есть.
5) вот тут просто +100500. Хотя бы ip-сокеты/listener-ы стандартизировали.
5) тут можно много копий поломать. К примеру, проакторный Asio я бы не сильно хотел видеть в качестве реализации сети в стандарте. Плюс, говоря о сети, нужно думать и о эффективной работы, неблокирующем режиме и мультиплексировании… А вот тут некая система Windows выделяется своим IOCP (без спора — что эффективнее)...
Очень много интересов сошлось на этом proposal. Все хотят сеть, но у всех разные желания: производительность, отзывчивость, простота, сопрограммы, единый интерфес с многопоточностью/stl, расширяемость и т.д.
Всей душой болею за это предложение и при том не я один. Должно получиться очень красиво и правильно, не не известно к какому году.
6*) нормальная функциональная поддержка для контейнеров чтобы можно было писать:
m.filter(...).map(...).reduce(...)
(но мы знаем, что это невозможно, пока не будет ranges, а ranges каким-то образом завязаны на концепты)
6**) Вдобавок к (непринятой в 17м стандарте) возможности вызывать метод класса как method(object, arg)
, добавить аналог this для аргументов из C#, когда один из аргументов для функции можно написать через точку слева, как будто это вызов метода: arg1.func(arg2)
. Это позволит писать функции для работы с объектами, избегая огорода из скобок.
6***) Убрать неконсистентное требование того, что временные объекты обязаны быть константными — чтобы заработало вот такое: func(C())
, где C()
— временный объект, который принимается в функцию по неконстантной ссылке.
Потому что сегодня приходится размазывать это на две строки:
C c = C(); func(c);
чему не видно никакого оправдания.
6**) Вдобавок к (непринятой в 17м стандарте) возможности вызывать метод класса как method(object, arg), добавить аналог this для аргументов из C#, когда один из аргументов для функции можно написать через точку слева, как будто это вызов метода: arg1.func(arg2).
Этим занимаются Страуструп и Габриель Дос Райс, но предложению предстоит ещё несколько итераций доработки. Вот последний proposal на эту тему http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0416r0.pdf
Не могу в данный момент проверить, но не должно ли if (std::lock_guard lock(m), !container.empty()) делать тоже самое уже сейчас?
В случае if (std::lock_guard lock(m); !container.empty()) lock живет в течение всего if стеймента
Да, действительно. Я заставил пример собраться, но о семантике не подумал.
Зато постоянно добавляют кучу адовой эзотерики. Если раньше я мог сказать, что знаю С++, то теперь уже точно нет.
Интересно, помогает ли все это это языку? Если 10 лет назад на нем писали почти всё, то теперь он скатывается в нишевые направления — 3D движки, OpenCV, микроконтроллеры и немного десктопного Qt (сужу по заказам на UpWork).
Лично для меня, как стандартный C++/STL был непригодным к использованию 10 лет назад, так он и остается до сих пор (Строки, работа с файлами, сеть, потоки и т.д.). Даже долбаное кросплатформенное приложение с нативным GUI интерфейсом невозможно создать (когда там графический интерфейс появился, в начале 90х?). Утешает только Qt.
Игры тоже.
Сервера гораздо проще не высоконагружать, а горизонтально отмасштабировать, купив железа. А написать на языке, в котором как минимум строки Юникод поддерживают, не говоря уже о встроенной поддержке БД и т.д.
Сервер с C10k есть прямо в статье https://en.wikipedia.org/wiki/C10k_problem это некий MigratoryData на Java на 10–12млн подключений.
Браузеры… вполне успешно пишутся на C# и Java.
затем:
Я не знаю, на чем написаны конкретные браузеры, но не вижу большой проблемы написать их на C# или Java.Так пишутся или «не знаю, но обсуждаю»?
Про С10к Я могу привести гораздо более развёрнутую статью с французской вики где про MigratoryData даже и не сказано. Что такие сервера есть, я уверен, есть вполне успешный сервер на питоне умеющий в С10k, но в основе они всё равно обращаются к чисто системным вызовам epoll/kqueue/iocp и так далее, но это не часть «стандартной» Java, почему я и попросил ссылку. Да и MigratoryData целиком коммерческий, может поэтому по нему очень мало информации в интернетах, в отличии от остальных (с десяток, не меньше, и бесплатных).
Насчёт серверов общего назначения — на Java, скорее всего, нет. Есть различные контейнеры приложений (tomcat/jetty/...), сколько они держат подключений на практике — не знаю, не интересовался. С учётом того, что основная их задача — не отдача статики, а отработка запроса в приложениии — проблемы раньше упрутся в особенности приложения.
Ну а сервер общего назначения, выдерживающий c10k я знаю один, но он не на плюсах а на голом C написан (nginx). На плюсах есть аналог?
(epoll на Linux 2.6, kqueue на FreeBSD/MacOSX, Overlapped IO на MS Windows)
И что? Asio там не используется, Nimble — косвенное заключение да и за чаем разработчика спрашивал, а исходники EvoStreamer до того как он стал закрытым доступны: crtmpsserver, там тоже нет Asio. Правда, думаю, сейчас Evo от своего прародителя далеко ушёл.
А по своему опыту: нужно было написать молотилку запросов HTTP (много мелких) Asio раскачать сильно не получилось, упирался в ~250к RPS, тогда как на libev (у него встроенный C++ интерфейс есть) запросто получилось около 600к RPS. Правда это на 1k подключений, на 10к снизилось где-то в полтора-два раза, на обоих.
Минус решений отличных от libuv и asio: для Windows, по сути, приходится писать свой код.
Ну и ещё, Asio и libuv сильно обмазывают epoll снаружи, что бы его использование походило по интерфейсу на IOCP
https://habrahabr.ru/company/mailru/blog/191756/
https://github.com/mamchits/phantom
На нем еще построена тулза для нагрузочного тестирования Яндекс.танк.
Вообще я был бы рад, если бы они развивались, но нужда сообщества в них достаточно мала, также как и интерес крупных компаний :(
https://android-review.googlesource.com/#/c/247370/5/compiler/optimizing/code_generator.cc
В андроиде на самом деле почти весь user-space на плюсах.
На C в Android'е — только некоторое количество legacy-кода (взятого часто из разных BSD).
Вообще весь Android — это немного «театр абсурда»: почти весь Android — написан на C++, большинство популярных программ — тоже. Но взаимодействовать им приходится через Java'у обходя кучу костылей, нужных для этого монстра. Вот что бывает когда PR ставят выше технической целесообразности… с другой стороны если бы не PR, то Android фиг бы «взлетел»: его ранние версии, по большому счёту, были весьма убоги, если бы не волшебное слово «Гугл», то не факт что его бы кто-либо сейчас ещё использовал…
Даже долбаное кросплатформенное приложение с нативным GUI интерфейсом невозможно создать
Единственный язык, который я вспомнил с ходу, со своей собственной реализацией кроссплатформенного GUI — это Java. И, знаете, как-то оно меня тоже не радует (как пользователя). Уж лучше Qt или другой «универсальный» тулкит.
Это реально "горшочек". Одно только внедрение аналитической геометрии в стандарт. Хотя ей самое место в отдельном пакете. Т.е. в стандарт тащат много, но не всегда то, что реально нужно.
А я не говорю, что аналитическая геометрия это плохо. Я говорю, что ей место в отдельной либе. Пусть она поддерживается комитетом, не вопрос. Но отдельно. Иначе такими темпами в стандарт захотят 3Д-движок, физический движок, библиотеку акторов и что-нибудь ещё. И тогда стандарт будет не 1600 а 16000 страниц.
Мне кажется это вопрос оформления/вкуса/дизайна/скрипта-сборки-документа. О чем спор то? :)
Как минимум, если что-то попадает в стандарт самого языка — это оттуда потом уже хрен выкинешь или нормально поменяешь — надо будет таскать с собой вечно в обратно-совместимом виде. А чем менее фундаментальна библиотека, тем больше шансов что она быстро устареет и станет бесполезной.
Проблема в том, что это просто тяжело осилить одним куском. Это примерно как God Class. Класс, отвечающий за всё и сразу. А помнить о всех тёмных уголках стандарта придётся — даже тем, кому эти мат. функции не нужны.
Был сеттер:
void SomeClass::SetName(const std::string& first, const std::string& last);
Теперь, в новом стандарте, есть move сематика. Теперь в лоб, получается, что для того, чтобы работали идеи авторов стандарта нужно писать что-то типа того:
void SomeClass::SetName(const std::string& first, const std::string& last);
void SomeClass::SetName(std::string&& first, const std::string& last);
void SomeClass::SetName(const std::string& first, std::string&& last);
void SomeClass::SetName(std::string&& first, std::string&& last);
т.е., на лицо, логарифмический рост количества перегрузок функций.
Я понимаю, что нужно теперь писать вот так:
void SomeClass::SetName(std::string first, std::string last);
и компилятор сам разберется, но что бы это понять и убедиться что это работает, нужно столько сил и времени. В итоге имеем такие затыки через каждые 20 строчек.
Просто: потому что copy elision 1. и так реализован в основных компиляторах 2. является требованием с++17.
> В итоге имеем такие затыки через каждые 20 строчек
Вот когда замерите, увидите что у вас программа 30+% времени перегоняет пустое в порожнее, туда-обратно копирует и жрет оперативку как хром, тогда у вас будет замечательный инструментарий в виде lvalue reference/rvalue/copy elision для оптимизации копирования и использования памяти. А заниматься микрооптимизациями, да еще и без четкого понимания как это всё работает — вредить себе и проекту.
Сценариев, где неявно используется семантика сдвига вместо копирования, не так уж и много и наткнуться на ошибки при их использовании попросту невозможно: это требует ручного некорректного определения мув-семантики в используемых классах.
void SomeClass::SetName(std::string first, std::string last)
{
_first = std::move(first);
_last = std::move(last);
}
Поскольку move очень дешев, то это будет практически оптимально для всех случаев:
foo->setName(first, last);
foo->setName(std::move(first), std::move(last));
foo->setName("Ivan", "Pupkin");
По-моему у каждого языка есть свои плюсы и минусы и, следовательно, он подбирается исходя из требований решаемой задачи.
c++ отлично подходит, если нужно написать какую-нибудь высокопроизводительную и сложую систему, но зачем его использовать там, где есть более удобные инструменты, пусть даже это и отразится в меньшей производительности и большем потреблении памяти, когда это не является критичным.
Графику наврядли получится стандартизировать. Слишком большой выбор: есть Qt, WxWidgets, Gtk, EFL и многие другие.
Предложение очень активно обсуждалось в Оулу, вносилось множество правок. Есть шанс через пару лет увидеть в std::experimental::
В отличие от многих новых языков перед С++ проблема привлечения новых пользователей стоит не так остро, поэтому особо снижать порог вхождения не требуется.
Для «простых смертных» есть куча других языков, которые сознательно стараются упростить процесс разработки (порой, правда, ценой производительности/гибкости/узкой специализации)
В первую очередь для обучения. Детей, школьников. А то они спрашивают "а как мне нарисовать черепашку?" и получив ответ с отсылкой к каким-то непонятным библиотекам, выбирают другой язык.
Ну и в таком случае, надо отсылать к понятным библиотекам.
Для графического интерфейса, в мире С++ есть решения. Тот же Qt.
Если нужен быстрый старт, то берем Qt Creator и вперед. Подозреваю что подключить Qt в других IDE тоже не особо сложно.
Можно сделаьт класс обёртку, с методами — а под капотом делать бекенды которые могут с любой БД работать, хоть mongo, хоть oracle
— что теперь каждые 3 года, когда вводят новые фишки в C++, миллионы строк кода готовых оттестированных кодобаз (библиотеки, игровые движки) должны кинуться обновлять свои сорсы под эти фишки, рискнув стабильностью своих продуктов?
так и представляю: миллионы коммитов в древних не тронутых файлах, где меняется самое простое допустим xx::yy::zz::iterator на auto.
— или эти новые фишки задумываются под новые проекты?
но подождите, какие новые проекты, если уже всё изобретено. Пропагандируется же: не изобретай велосипед, бери и пользуйся готовым.
— или предполагается совмещение стилей С++03 с новыми стандартами в одном продукте?
т.е. листаешь ты сорсы, в одном файле xx::yy::zz::iterator, в другом — auto. Очень всё лаконично же и в одном стиле, не код, а загляденье.
Единственное фундаментальное изменение — это move semantics.
А так да, можно писать в стиле C++03.
boost'у это можно простить, потому что они выжали максимум средствами языка, но бюро стандарта сделали эти костыли стандартом.
Предлагают заменять на auto — тогда каждый раз нужно лезть в подсказку и узнавать что это за тип, теряется концепция быстрого чтения кода.
Move semantics прочитал здесь. «гениально», стреляем себе в ногу дальше. Теперь каждый раз нужно лезть в класс, и смотреть, а нет ли у него move semantics внутри, чтобы знать, что этот класс нельзя использовать после копирования foo&.
нет ли у него move semantics внутри, чтобы знать, что этот класс нельзя использовать после копирования foo&
Я вас не понял, распишите пожалуйста проблему подробнее.
auto считаю злом.
Замечание про move semantics не понял. Любой старый класс будет неявно поддерживать перемещение, если не указано обратное. И всё будет работать корректно, но до тех пор, пока конструкторы копирования и перемещения будут выполнять только то, что они должны, без побочных действий.
auto хорош для локальных переменных, возможно, некоторых приватных методов. Но нельзя допускать, чтобы публичный метод возвращал auto, кроме редких случаев, когда используются шаблоны и вывод громоздок или крайне затруднителен.
Весь этот огород с move semantics, когда однозначно не понятно, в каких случаях класс копируется, а в каких перемещаются кишки, и тогда старая переменная не валидна для использования, потому что внутренний хендл переместили. И что тогда в каждую функцию File добавлять проверку на валидность хендла? Адская избыточность. Зачем эти усложнения?
В той хабра статье уповают только на использование динамической памяти там, где она мол не нужна.
Какие сейчас проблемы с памятью? У меня на сервере свой MM, предвыделенные чанки (расширяются автоматически) от 8 байт до 32кбайт с шагом по экспоненте, в неблокирующем ring buffer'e.
Выделить 8-32кбайт на хендл затрат проц времени для ММ около нуля, зато получаем более управляемый код. Это я имею ввиду использовать хендл в обвязке умного указателя.
Сейчас тенденция везде использовать умные указатели для управляемости и читабельности, и я не понимаю смысла ввода move semantics в виду этой тенденции, чтобы ворочать сырыми указателями в не обвязанных классах, создавая неопределенное поведение классу для пользователя извне, не знакомому с внутренней работой класса.
Да почему непонятно-то? Как раз наоборот, все предельно просто.
"Кишки перемещаются" ровно в двух случаях:
- когда старое значение более не доступно;
- когда программист явно написал
std::move
Если вы не будете использовать std::move
— вы никогда не натолкнетесь на невалидные переменные.
Сейчас тенденция везде использовать умные указатели для управляемости и читабельности, и я не понимаю смысла ввода move semantics в виду этой тенденции
Все просто: move semantics придумали чтобы сделать нормальные умные указатели. К примеру, uniqie_ptr
без семантики перемещения нельзя было бы использовать в STL-контейнерах.
Все просто: move semantics придумали чтобы сделать нормальные умные указатели. К примеру, uniqie_ptr без семантики перемещения нельзя было бы использовать в STL-контейнерах.
Не только для них. Чтобы не иметь копирований на ровном месте, где их быть не должно.
И слава богам! Замена COW строки на строку со small-buffer optimization в двух абсолютно различных проектах давала прирост производительности более 10% (мой личный опыт). Знакомые из других проектов подтверждают прирост в производительности.
Для не верящих есть целая статья http://www.gotw.ca/publications/optimizations.htm
Если вы таскаете по стеку структуру на 100500 полей, вы делаете что-то не так. А мув служит для почти бесплатного перемещения контейнеров, у которых маленьая часть на стеке и большая в куче. Простой пример — вектор на пару сотен килобайт. Без мува, при отдаче владения в функцию, у вас будет копирование. Толстое. С выделением ещё одного куска на пару сотен килобайт и копированием всего содержимого. С мувом — старый вектор отдаст новому владение буфером. И будет "копирование" 1 указателя. С занулением его на старом месте.
мув служит для почти бесплатного перемещения контейнеров, у которых маленьая часть на стеке и большая в куче
Бесплатного для того кто использует, а для того кто пишет эти классы :)?
Что я могу сказать. Не нравится — не используйте. Как указывалось много раз выше, перемещение включается в конкретных, чётко описанных ситуациях.
Если же вопрос в "плохо спроектировано" — в каком-то смысле да. Основная проблема — старое местоположение остаётся доступным, даже после мува. Если бы это блокировалось компилятором, то и мув-конструкторов, пожалуй, не понадобилось бы. Но сделать старое местоположение недоступным в рамках семантики С++ нельзя — любое значение является прежде всего пачкой байт, которые можно копировать во все концы. А конструкторы перемещения, операторы присваивания и т.п. представляют собой своего рода хуки на эти действия.
Почти бесплатно.
Современная концепция состоит в том, что управление ресурсами должно осуществляться в специальных листовых классах. Это могут быть умные указатели или самописные классы. В этих классах да, придётся самому написать все 5 спец-методов (см "Rule of Five"). Но таких классов должно быть немного (по одному на каждый вид ресурса, причём для самых распространённых ресурсов "память" и "открытый файл" такие менежджеры уже реслизованы в стандартной библиотеке).
Остальные же классы (которых большинство) вообще не должны заниматься управлением ресурсами и не должны реализовывать спец функции сами. Они просто должны включать в себя классы-менеджеры ресурсов, и компилятор всё остальное сделает сам.
Эта концепция известна под именем "Rule of Zero".
Теперь каждый раз нужно лезть в класс, и смотреть, а нет ли у него move semantics внутри, чтобы знать, что этот класс нельзя использовать после копирования foo&.
Мне кажется вы не поняли move сементику. Проблемы никакой нет. Прочитайте еще раз до полного просветления.
Не нужно кидаться и обеспечивать поддержку всего-всего на свете.
Как-то в сфере веба довольно часто всё обновляется, но живет же как-то эта сфера.
std::string::data(), возвращающий неконстантый char* (УРА!);Может я не сталкивался просто, а что не так было с константностью этого метода? Вполне логично, что получая «сырые» потроха строки их нельзя менять, как мне кажется.
std::string&
вместо const std::string&
, но при этом строку не меняющие. Кому от этого лучше станет?Мне вот интересно в какой версии C++ конструктор string'а станет наконец-то
constexpr
… В 20й? Или в 50й?Как писал Джоел: Интересно, что историю развития C++ можно описать как историю затыкания дырок в абстракции строк. Уж не знаю, отчего бы не добавить к языку элементарный класс строчек.
Пока что наблюдение продолжает иметь силу :-)
Если вы знаете, как сделать constexpr-конструктор для класса, владеющего памятью на куче, срочно оформляйте proposal! Это же столько дряни в компайл-тайм перетечет!
std::string_view имеет в C++17 constexpr конструкторы и операторы, но часть операций зависит от CharTraits, где аналогичные операторы тоже должны быть помечены как constexpr.
То, что его нельзя будет затем ресайзить?
Вариант 2: сделать конвертируемую в std::string constexpr-оболочку поверх const char*. Тогда оболочка-то будет constexpr, а сам string будет создаваться из неё в рантайме. Т.е. тоже бесполезно.
Кстати, из чистого интереса. std::vector, std::string научились конструироваться из сырого буфера и отдавать свой сырой буфер?
The pointer returned points to the internal array currently used by the string object to store the characters that conform its value.
Both string::data and string::c_str are synonyms and return the same value.
http://www.cplusplus.com/reference/string/string/data/
Или нужно что-то наподобие unique_ptr, который принимает готовый указатель и владеет им, пока явно не отдашь (release)? Но зачем?
Ну, реализовать using
-то теперь можно без проблем:
#define using(x) if(x; 1)
Для while
такое уже есть, называется for
:)
А вот это действительно странно, объявление переменных в условии это общее свойство if/switch/while/for, а расширили синтаксис только для if и switch. Причём добавлять в for может не захотели из-за изменения его структуры, а вот чего while пропустили непонятно.
Я не о i:
for (int i = 0; i < 10; ++i) ...
А о e:
for (int i = 0; error_code e = check_i(i); ++i) ...
Могли бы расширить (для единообразия, практической пользы тут не так уж и много) до:
for (int i = 0; error_code e = check_i(i); has_error(e); ++i) ...
(здесь e вычисляется и проверяется на каждой итерации)
и тут же:
“Осторожно: string_view не гарантирует, что строчка, которая в нем хранится, оканчивается на символ '\0', так что не стоит использовать функции наподобие string_view::data() в местах, где необходимо передавать нуль-терминированные строчки.»
почему бы сразу не рассмотреть std::string get_vendor_from_id(std::string_view id)?
std::string_view id{"Vasya Pupkin:Super Stuff"};
std::string_view vasya_sb = id.substr(0, id.find_last_of(':'));
std::string vasya{ vasya_sb };
assert(vasya == "Vasya Pupkin");
Ну и компоненты, как в Делфи.
Злой тролль-сишник так и не осиливший плюсы.
for(auto [key,value]: my_map) {
}
Для каких переменных используется именование с подчеркиванием на конце? Например, valuesmutex.
Интересно что люди одновременно хоронят С++ за лишние фичи и за из отсутствие. Причем одни и те же фичи одновременно ругают и хвалят куча людей.
Кто не верит в комитет — вступайте и действуйте ИМХО. Кто не верит в С++ — развивайте/пользуйтесь альтернативами
Проблема не в реализации (реализация уже год как есть в GCC), а в том, что комитет не может никак окончательно договориться как это должно работать. Постоянно возникают новые возражения и соображения. Ну и хотят дождаться практического опыта с этой реализацией.
Быстрее бы до ума довели.
Представим ситуацию поиска элемента где-либо (например, в массиве):
item a = ...;
for (...)
{
item b = ...;
if (a == b)
{
// We've found the item
do_something ();
break;
}
}
// We are here in both cases when we've found <a> and when we haven't.
Без введения дополнительных проверок или использования goto различить причины «выхода» из цикла невозможно.
Проблема курицы и яйца:
for
...else
люди не используют потому что не знают, а раз не знают — то и в стандарт добавить её некому…if (auto result_it = std::find(std::begin(container), std::end(container), a)
; result_it != std::end(container)) {
do_something();
} else {
...
}
[&]{
for (...) {
...
if (...)
return;
}
// Else part
...
}();
ну и итерируйтесь не до end(), а до advance(begin(),N). Для random access итераторов (например, в векторе) можно даже писать begin()+N.
Если там какая-то совсем сложная логика, то проще либо на функции разбить (и возвращаться через return), либо (на крайний случай) булевый флаг завести. И то вызовет меньше вопросов
А если уж и заводить такой функционал, то назвать как минимум не else, а interrupted/finished, например
if (...)
for (...)
one_code_line_action;
else
...
Навскидку могу предложить такие варианты:
1. Ввести новое ключевое слово. Не самый лучший вариант, однако, например, в С++11 появилось же auto. Вполне возможно, что раньше оно использовалось где-то в программах.
2. Использовать комбинацию ключевых слов, например:
for (...)
{...}
for else
{...}
3. Использовать >> или ->. На мой взгляд, внешне будет неплохии решением:
for (int i = 0; i < 5; ++i)
if (get_byte_from_stream () == value)
break;
>>
send_byte_to_stream (value);
...
for (int i = 0; i < 5; ++i)
if (get_byte_from_stream () == value)
break;
->
send_byte_to_stream (value);
4. Использовать ключевое слово continue. По текущему синтаксису оно используется так:
continue;
Здесь же будет
continue statement
for (int i = 0; i < 5; ++i)
if (get_byte_from_stream () == value)
break;
continue
send_byte_to_stream (value);
Не самый лучший вариант, однако, например, в С++11 появилось же auto.
auto
было ключевым словом с 70х годов, однако.Вы бы лучше бы какой-нибудь
decltype
вспомнили…А насчёт комбинации ключевых слов можно своровать подход у Ады и использовать
or else
(или даже просто or
).P.S. Вы не поверите, но и
and
и or
и даже not
— в C++ являются ключевыми словами!auto было ключевым словом с 70х годов, однако.
эх, забыл, каюсь) думал про decltype, но auto же короче)
Ну, на самом деле, сейчас (лично) мне нравится больше вариант continue либо >> (->). Но, по большому счету, это все равно ничего не изменит)
int or = 5;
int and = or + 1;
and++;
MSVC, конечно, далеко не истина в последней инстанции, но это означает, что где-то могут быть программы, использующие такие имена.
- Выход из вложенных циклов без goto планируется?
- Планируется ли 'continue' блок для for/while, опять же, чтобы избежать goto и отличить нормальный выход из цикла от break?
break n;
где n — число вложенных циклов, из которых надо выйти (и, например, 0 — для выхода из всех циклов, сколько бы их не было).
Когда стал изучать С++ (С), мне достаточно успешно внушили, что лучше заводить флаг и пару ифов, но только не goto (что типа компилятор разберется и оптимизирует, а читать будет легче). Потом посмотрел исходники больших проектов и увидел goto там. С тех пор использую сам при необходимости без особых зазрений совести. Но встречал людей, для которых goto — как начало третьей мировой войны.
Но встречал людей, для которых goto — как начало третьей мировой войны.Угу. При этом те же самые люди без зазрения совести используют,
switch
, break
, continue
— и канючат на тему «расширения их функциональности».Карго культ какой-то. Как будто проблема
goto
— не в усложнении логики программы, а в том, что это слово портит карму.Да,
goto
следует избегать, так как они запутывают логику — но компилятору они ничуть не мешают (он всё равно все ваши циклы превращает в базовые блоки и goto
), а запутать логику работы флажки могут похлеще любого goto
. Собственно любая программа с любым количеством goto
может быть превращена в программу без goto
добавлением одной переменной (next_basic_block
, ага), что и показывает ущербность подхода с флажками.Ещё раз, для тех кто в танке: «программа с одним, всего лишь одним флажком (правда многопозиционным) может быть также сложна в отладке, как любая программа с любым количеством
goto
». Прэлестно, правда?Это ещё рефакторить менее удобно. Добавился цикл — надо будет посмотреть не используются ли внутри break/continue n. Лучше уж с именованными метками.
Не думаю, что это хорошо бы читалось. Лучше сделать как в Rust, где можно отметить цикл меткой и использовать break 'label.
Еще до Rust так было сделано в Java.
По-поводу конструкции if (init; condition) ...
. Почему не сделали как в некоторых других языках, например, вот так: let { init; } if (condition) ...
или if (condition) ... where { init; }
?
let… where
что бы ключевые слова новые не вводить?
Неясно, чем
let { init; } if (condition) { ... }
хоть немного лучше, чем сегодняшнее
{ init; if (condition) { ... } }
Второе семантически более соответствует сегодняшним представлениям об области видимости и времени жизни переменных.
А where
в хвосте — вообще жуть.
Это надо будет мотать в конец блока чтобы посмотреть, что же там за переменные в начале.
let (init) something
, которое было бы семантически было бы эквивалентно { init; something }
Тогда можно было бы использовать его не только для if и while, а в любом подобном месте. Скажем, в примере с захватом мьютекса мне может и не надо ничего проверять, я просто хочу захватить мьютекс, выполнить какое-то действие, а потом мьютекс отпустить.
let (std::lock_guard<std::mutex> lock(m)) if (!container.empty()) { // do something }
let (std::lock_guard<std::mutex> lock(m)) { // do something }
{
std::lock_guard<std::mutex> lock(m);
// do something
}
Цель же введения этого нового способа записи в стандарт в банальной экономии строк кода.
if (ResultCode rc = DoSomething(); rc.Failed())
{
// обрабатываем ошибку, отваливаем
}
В рамках текущего стандарта это выглядит так:
ResultCode rc;
if ((rc = DoSomething()).Failed())
{
// Обрабатываем ошибку
}
Довольно, как это говорят, ugly. Предвосхищая вопрос. Такие конструкции могут использоваться в библиотечных «обёртках» над стандартной обработкой ошибок, логированием и т. п. То есть типа:
CHECK_SUCCEEDED_RETURN(DoSomething()) << «Приключилась адская хрень»;
интересно, как выкрутились, т.к. Страуструп декларировал:
Язык программирования С++ 386 стр.
«Обратите внимание, что параметры шаблона класса (в отличие от шаблона функ-
функции) никогда не выводятся. Причина заключается в том, что гибкость, обеспечивае-
обеспечиваемая наличием нескольких конструкторов класса, во многих случаях является непреодолимым
препятствием на пути такого выведения, и еще в большем числе случаев
выведение неоднозначно. Специализация предоставляет механизм неявного выбора
из различных реализаций класса (§ 13.5).»
Note that class template parameters are never deduced. The reason is that the flexibility pro-
vided by several constructors for a class would make such deduction impossible in many cases and
obscure in many more. Instead, specialization (§25.3) provides a mechanism for implicitly choos-
ing between alternative definitions of a template. If we need to create an object of a deduced type,
we can often do that by calling a function to do the deduction (and creation). For example, con-
sider a simple variant of the standard library’s make_pair() (§34.2.4.1):
template<typename T1, typename T2>
pair<T1,T2> make_pair(T1 a, T2 b)
{
return {a,b};
}
auto x = make_pair(1,2);
auto y = make_pair(string(«New York»),7.7);
// x is a pair<int,int>
// y is a pair<str ing,double>
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r2.html#uec
Грубо говоря, принцип аналогичен вызову функций, только для конструкторов.
Необходимость же такой возможности возникла из-за лямбд, тип которых в явном виде записать невозможно.
Мы организовываем 5 октября 2016г открытую встречу российской рабочей группы по стандартизации С++: поговорим о перспективах С++, расскажем об успехах, новинках,
Подробности: Открытая встреча рабочей группы по стандартизации С++
Последние новости о развитии C++