Comments 112
Попробуй новые языки. Rust, например, предлагает безопасность намного выше, чем C++, не имеет тонны Легаси, а также не фанатичны к выборы парадигмы. D сейчас есть в варианте без сборщика мусора. А если нужно что-то простое, так ещё и "поближе" к железу, то есть старый добрый C с расширениями GCC. Там и автовывод типов добавили, и безопасные макросы, и дженерики, и даже RAII.
А D язык хороший, но «не взлетел», к сожалению.
Alexey_Alive, направьте меня в сторону автовывода типов? Не нашел среди C Extensions в документации GCC. RAII — это вы об атрибуте __cleanup__
?
Да, я про cleanup. Это некий аналог RAII, ибо в Си нет классов. По поводу вывода типов, в Gcc есть аналоги auto и decltype из cpp: __auto_type и typeof. Благодаря им можно писать безопасные макросы, например (в GCC в ({ }) последнее выражение возвращается.)
#define max(a,b) \
({ __auto_type _a = (a); \
__auto_type _b = (b); \
_a > _b ? _a : _b; }) <source>
Поддерживаю. Но самое трудное — согласовать свои желания с коллегами. Чтобы не получилось ситуации, что один использует с++03, а другой фигачит лямбды с вариадиками.
Delphi/Pascal конечно же
class singleton
{
public:
static singleton &instance()
{
static singleton inst;
return inst;
}
private:
singleton() {}
singleton(const singleton &) = delete;
singleton(singleton &&) noexcept = delete;
singleton &operator=(const singleton &) = delete;
singleton &operator=(singleton &&) noexcept = delete;
};
Что касается «с удалением — труба», так это тоже просто от плохого знания предмета:
Destructors (12.4) for initialized objects of static storage duration (declared at block scope or at namespace scope) are called as a result of returning from main and as a result of calling exit (18.3). These objects are destroyed in the reverse order of the completion of their constructor or of the completion of their dynamic initialization. If an object is initialized statically, the object is destroyed in the same order as if the object was dynamically initialized. For an object of array or class type, all subobjects of that object are destroyed before any local object with static storage duration initialized during the construction of the sub- objects is destroyed.
Я всегда замечал, что наиболее ярые критики C++, которые «ни за что и никогда», просто плохо знакомы с языком.
Так в этом и состоит суть критики: сложно осилить.
Я каждый день пишу на С++ и только на С++.
И я его сегодня знаю хуже чем лет пять назад.
Работа комитета вызывает больше негатива, чем позитива.
Да, язык нужно развивать. Но комитет ударился в впихивание всего подряд. Половину нового можно выкинуть, потому что оно является синтаксическим сахаром, мало нужным в повседневной работе. По сути просто пытаются один язык превратить в другой. На выходе получается гребаный франкенштейн. Не удивлюсь если через пару лет в стандарте внезапно появится GC. Не, ну а чо, полезная же штука!
Ну, я не согласен. Например, spaceship operator — это синтаксический сахар? Да. Нужно ли выкинуть? ИМХО нет, потому что он избавляет от написания просто тонны boilerplate кода. Да, конечно, можно писать "по старинке", педалить все вот эти operator ==, !=, <, >, а ещё про friend operator не забыть, все по новой… Но зачем? В чем профит? А "ниасилить" тоже можно по-разному, можно не разбираться в тонкостях SFINAE, а можно быть неспособным реализовать синглтон без утечки памяти или испытывать сакральную боязнь object slicing'а, потому что где-то прочёл, что это "плохо", а почему именно это плохо и в каких случаях — не понимаешь, а ведь эти вещи ещё из C++98. В общем, незнание незнанию рознь.
Но вот тот же структурный биндинг из статьи — нафиг не нужен. Взять из переменной набор, вместо того чтобы обращаться к полям через переменную, что не составляет труда. К тому же тот же with из delphi гораздо более адекватное и красивое решение, если уж настолько критично не писать название переменной для доступа к члену…
Вообще, ИМХО, комитету не хватает некой дополнительной «проверки временем». То есть добавили фичу, если через 5 лет этой фичей не пользуются большинство крупных игроков на С++ рынке — она выносится еще раз на обсуждение и если весомых доводов её оставить нет — depricated и досвидания в следующей редакции.
Ну это я так понимаю скорее для всяких кортежей, особенно если в них ссылки. Вместо написания простынки из создания временных tuple и std::get.
deprecated и досвидания в следующей редакцииВерный путь убить язык, потому что если какой-то код будет компиляться с /std: с++14, но не /std:c++17 — будет расти фрагментация.
Чтобы не было ситуаций, когда «Так, сегодня мы утверждаем переход в релиз фичи Х. Кстати, кто уже делал проекты с её использованием?.. Кто хотя бы пробовал?.. Ясно, переносим обсуждение на следующую встречу».
Напомню, примерно так было с одной из фич на последней встрече.
Засрать язык фичами, которые использует два с половиной фаната — это тоже верный способ убить язык.
То есть добавили фичу, если через 5 лет этой фичей не пользуются большинство крупных игроков на С++ рынке — она выносится еще раз на обсуждение и если весомых доводов её оставить нет — depricated и досвидания в следующей редакции.
ну примерно так и происходит, только не 5 лет, а 2-3 года. Или вы никогда не пытались пользоваться бустом/experimental?
Засрать язык фичами, которые использует два с половиной фаната — это тоже верный способ убить язык.
какие фичи с++17, по-вашему, используют «два с половиной фаната»? С++14? С++20?
А если серьезно вопрос достаточно сложный. Я на него не могу твердо ответить.
На любой мой ответ можно будет возразить: «Вот код, где это используется». Статистики то у меня нет, только ощущение от работы в разных командах плюсовиков.
Что там насчет «Garbage collector support»? :))
Это с++11, там да, есть несколько редко используемых фич. Собственно, после него в методологии развития стандарта многое поменялось.
На любой мой ответ можно будет возразить: «Вот код, где это используется».
ну вы назовите фичи, которые, на ваш взгляд, почти никто не использует. Ваше мнение же должно быть основано на конкретных примерах?
Ваше мнение же должно быть основано на конкретных примерах?
Какое мнение? Я нигде не делал утверждение, что сейчас в стандарте есть фичи которые используется два калеки. Делать такое утверждение — большая ответственность. Надо быть большим экспертом. принимающим активное участие в анализе кодовой базы чтобы такое утверждение сделать.
Я сказал что делать такие фичи — ошибка. Обозначил тенденцию.
Нет, стандарт же вполне однозначно всё определяет.
О да, стандарт сила, кто бы возражал. Вспомните, к примеру, так прекрасно разобранную О'Двайром историю про gcc, clang, msvc и разные форматы декорирования имен.
Что касается структурного связывания, то у меня не хватает ума, чтобы без просмотра сгенерированного ассемблера наверняка понять, что и как создается.
Смартпоинтеры — логичное развитие голых указателей. По сути голые указатели не должны вообще хранится где-либо, кроме участка кода, который с ними непосредственно сейчас работает. Тут ни оверхеда по производительности нет, ни непредсказуемости.
А вот GC — совсем другая история. Но, повторюсь, все таки верю в светлое и надеюсь до имплементации GC в стандарте дело не дойдет.
В языке даже синглтон толком нельзя реализавать без утечки памяти.
Разверните эту свою мысль. А то есть мнение, что вы до C++ так и не дошли, так сказать, перед тем, как от него уходить.
Синглтон по определению не может вызвать утечку памяти. Просто потому что для утечки требуется бесконтрольное выделение памяти под всё новые объекты, а синглтон всегда один.
Писал на «сплюсплюс» тогда…
Теперь угас уж жар в крови:
На «Си» пишу, на «чистом С»…
P.S.
Статья хорошая, понравилась.
Знаю С, но как понять синтаксис нового C++ для меня загадка.
Вот статья которая очень хорошо описывает подобную ситуацию с С++ (инициализация)
habr.com/ru/post/438492
Программисты жалуются, что у них сложный код? Пусть простой пишут.
Да даже свой собственный код через некоторое время становится сложным для понимания/вспоминания.
Так что я предпочитаю использовать инструменты, которые как-то автоматизируют процесс, руками, конечно, хорошо, но, через некоторое время, это становится слишком «дорого».
Решить проблему можно только «внеязыковыми» средствами: архитектурой, продуманным дизайном и т.д. И вот тут есть интересный момент: можно добавлять в язык выразительные средства, а можно «опрощать» язык, чтобы уменьшать, так сказать, удельное логическое сопротивление на строчку кода. Получается, что кода-то очень много, но он «простой», в нем даже IDE может разобраться и построить схемы, навигацию и т.д. C — он изначально достаточно прост. А, к примеру, Java и Go сознательно сделаны такими.
Шутите? Это редчайшие события, как в тестах, так и в проде. В тестах хорошо если раз в год бывает сегфолт, в проде машин больше — там несколько случаев на сто тысяч запусков, но это в основном сбои железа, прежде всего памяти, они четко по машинам привязаны и при замене железа исчезают. Ассерты на логику да, могут вылетать но это с языком уже не связано.
У нас тут распределённая сборка есть, так что можно посмотреть кто и что компилирует. На глаз, примерно 80% попыток вообще не компилируются. Я не поленился, и просмотрел последние 50 фейлов, когда проект собрался, юниты запустились но не прошли:
В 41 случае сработал ассерт в тесте
Ещё четыре падения когда сработал ассерт в продуктовом коде: попытались засунуть null в словарь, что-то не вызвали, и т.п.
Ну и пять сегфолтов родимых.
Но вот индексная арифметика — да, до коммита в прод может сегфолтить или ассертится, согласен. Разыменование null тоже бывает. Но это в любом языке будет ассертится, это ошибки в логике и не очень понятно при чем тут плюсы. А конкретно специфичную для плюсов работу с памятью в C++11 до сегфолта довести очень сложно.
Абсолютно согласен, что юниты хорошая штука для любого языка, они помогают не держать в голове одновременно все требования. А если тесты хорошие, то даже можно обо всех требованиях и не знать — тесты подскажут что апи добавления комментов должно проверить не забанен ли пользователь, а алгоритм поиска пути обходит скалы и реки, и предпочитает дороги. Ну или что там продукт делает.
А конкретно специфичную для плюсов работу с памятью в C++11 до сегфолта довести очень сложно.
Я знаю имена как минимум пятерых, кто сегодня смог это сделать. Не думаю, что они к этому как-то специально стремились. Просто чуть ослабла концентрация, отвлёкся на задачу и привет.
Я знаю имена как минимум пятерых, кто сегодня смог это сделать.
Тогда Вы явно делаете что-то неправильно :)
Он весь реализован на базовом С++.
И он почти весь переехал в стандарт.
Кстати, далеко не самое плохое что внесли в стандарт.
boostВы же обещали без извращений! Внутри boost к ним как раз таки и приходится прибегать. Что бы этого избежать, отчасти и добавляют новые фичи в стандарт.
Он весь реализован на базовом С++.с кучей макросни и такого когда, на который смотреть больно.
Так что вопрос открыт: что такого добавили, что можно было без извращений реализовывать раньше?
Я резко не соглашусь с тем что C++ не является «современным языком». C++11 был радикальным шагом вперед, язык стал намного удобнее в использовании, более читабельным, более производительным. Ушла необходимость бороться с языком во многих местах. И сейчас C++17 — тоже большой шаг вперед. Не такой драматичный как C++11, но очень заметно упрощающий жизнь и убирающий потребность в некоторых критичные велосипедах.
Base foo3(bool c)
{
Derived a,b;
if (c) {
return std::move(a);
}
return std::move(b);
}
(если здесь предполагается что Derived это производный класс от Base) происходит object slicing. Заботиться при этом о реализации move semantics как-то уже излишне.Есть хоть какая-нибудь литература, чтобы попытаться сделать шаг через пропасть?
То есть по сути библиотеки и шаблоны создали новый язык программирования.
В итоге код на современном C++ ну явно нечеловеческий.
Он нечитаем.
100500 минусов в мою несуществующую карму, но это так.
habr.com/ru/post/166849
C++ успешен, т.к., вместо попытки предложить машинную модель, изобретенную разве что в процессе созерцания своего пупа, Бьярн начал с C и попытался развивать C далее, предоставляя больше техник обобщённого программирования, но в контексте рамок этой машинной модели. Машинная модель C очень проста. У вас есть память, где находятся сущности. У вас есть указатели на последовательные элементы памяти. Это очень просто для понимания. C++ сохраняет данную модель, но делает сущности, располагающиеся в памяти, более исчерпывающими, чем в машине C, т.к. C имеет ограниченный набор типов данных. А именно, C имеет структуры, предоставляющие разновидность расширяемой системы типов, но он не позволяет вам определять операции над структурами. Это ограничивает расширяемость системы типов. C++ существенно продвинул машинную модель C к действительно расширяемой системе типов.
return std::move(b);
не является бесполезным/вредным? Скотт Майерс в «Эффективный и современный С++. 42 рекомендации по использованию C++11 и C++14» писал, что в лучшем случае компилятор поймёт нас, хотя не обязан, а в худшем мы сломаем RVO/NRVO. Или в 17 стандарте что-то с тех пор кардинально поменялось?
компилятор, вероятно, попытается сделать всё, чтобы выбрать более оптимальный вариант вернуть значение, однако, если RVO/NRVO нельзя сделать или нельзя сделать неявный move, то значение будет копироваться (lvalue).
RVO/NRVO сломать очень легко, с неявным move есть как минимум defect report (CWG1579), который его запрещает, если тип функции и тип возвращаемого значения разные (даже если есть возможность сделать move), поэтому в некоторых случаях нужно явно писать move.
Можете посмотреть видео с cppcon, где есть чуть больше примеров: CppCon 2018: Arthur O'Dwyer “Return Value Optimization: Harder Than It Looks”
И поищите подробнее про диагностку у clang, например: -Wreturn-std-move
Рекомендации — это хороший вариант делать всё относительно хорошо в среднем (как минимум код будет работать), но бывает и так, что можно сделать более производительно/правильно, если разобраться, что за всем этим стоит на самом деле.
template<>
std::tuple_element_t<0, Foo> const& Foo::get<0>() const
{
return _bar;
}
ничем не отличается от этого:
template<>
std::tuple_element_t<0, Foo> & Foo::get<0>()
{
return _bar;
}
О таком clang даже из коробки с
-Wall
сообщает:warning: 'const' qualifier on reference type 'std::tuple_element_t<0, Foo>' (aka 'int &') has no effect [-Wignored-qualifiers]
.Теперь к делу. Structured binding имеет вполне понятные подводные камни со ссылками, и вы итак об этом должны были знать, потому что cppreference читали перед написанием статьи. Простой пример, надо обратить внимание на '
&
':#include <tuple>
std::tuple<int, float> foo();
int main()
{
auto [a, b] = foo();
}
превращается в:#include <tuple>
std::tuple<int, float> foo();
int main()
{
std::tuple<int, float> __foo7 = foo();
std::tuple_element<0, std::tuple<int, float> >::type& a = std::get<0UL>(__foo7);
std::tuple_element<0, std::tuple<float> >::type& b = std::get<1UL>(__foo7);
}
Вопрос: зачем при связывании подсовывать другой тип? Я не вижу ни одного нормального юзкейса, где бы это пригодилось — очень похоже на антипаттерн. Канонический пример, где был бы нужен SB вот такой:
if (auto [ iter, success ] = my_set.insert("Hello"); success) do_something_with(iter);
А теперь посмотрите только на свой пример:
Foo foo;
const auto& [f1] = foo;
Каким образом я, как читатель кода, пойму какой тип имеет
f1
(опустим вопрос использования SB для одной переменной), если я не видел метафункций для Foo
? Почему я должен предполагать, что передо мной ссылка на инт?В том плане, что привычная с виду const функция класса может вернуть не-const внутренний объект. И дальше приводится пример, как это поправить.
Как читатель кода поймет, что перед ним за тип? Достаточно просто, если читатель знает, что такое SB и помнит, что у него нет реализации по умолчанию для пользовательских типов, поэтому надо посмотреть в код/документацию по этому поводу.
Понятно, что со стандартными типами (tuple, pair, array), не надо подменять типы, но с пользовательскими отсутствие const там, где он по идее должен быть, может оказаться весьма неприятным.
P.S.
Для примера замените int& на пустой какой-нибудь класс Bar&, а потом передайте const Foo& в функцию, возьмите от него SB и проверьте, будет ли результат константной ссылкой.
Если все-таки темой статьи было скрытое обсуждение type deduction в разрезе SB, то тогда стоило так и назвать ее, меня бы тогда ничего не смутило. :) Потому что просто SB — то, на что я дал ссылку выше. Задуманное использование максимально простое: там, где раньше надо было бы использовать
std::tie
, теперь можно наставить квадратных скобок.А с предложенным кодом у меня есть два варианта прочтения:
1) Если действительно надо связывать одну переменную с другой вот таким способом… это ужасное нарушение инварианта класса. Потому что
int& _bar
из примера — приватное поле Foo
, его действительно стоит таким образом выставлять наружу? Успокойте что это не так. Это даже закрывая глаза на нецелевое использование инструмента, которым планировалось легко возвращать несколько переменных из одной функции.2) Наверное, связывание будет минимум на 2-3 переменные, и что тогда, чтобы прочесть такой код и быть уверенным, что всё в порядке, мне надо пойти в определения этих 2-3 типов и посмотреть где же у них там
std::tuple_element_t<i, Foo>
мелькает? И потом держать это в голове? Наивно предполагать, что так хоть кто-то будет делать больше одного раза. Это очень хрупкий код, который теряет в самодокументируемости практически сразу.По мне так и то, и то — повышает хрупкость кода без видимых плюсов. В будущем, когда больше людей так будут делать, это должно стать антипаттерном.
«Скользкие» места C++17