Comments 315
Сейчас выглядит ну очень олдскульно и зачастую может быть заменена Boost-ом или POCO. Тем не менее, если знаний C++ недостаточно для разбирательства с потрохами Boost-а на какой-нибудь богом забытой платформе, то ACE вполне может быть достойным выбором и сегодня. ИМХО. конечно.
Он очень красивый (нет)
explicit vector(size_type _Count)
: _Mybase()
{ // construct from _Count * value_type()
if (_Buy(_Count))
{ // nonzero, fill it
_Alty _Alval(this->_Getal());
_TRY_BEGIN
_Uninitialized_default_fill_n(this->_Myfirst, _Count, _Alval);
this->_Mylast += _Count;
_CATCH_ALL
_Tidy();
_RERAISE;
_CATCH_END
}
}
* нижние подчеркивания перед всем что только можно — чтобы защитить себя от пользователей, пишущих макросы в нижнем регистре
* вместо throw/try/catch — макросы, так как многие пользователи используют стандартную библиотеку с выключенными исключениями
* большие и длинные тела if — оптимизация под конкретный компилятор (разработчик знает где у него hot path и знает какую ветку компилятор делает hot path). Некоторые стандартные библиотеки имеют в телах goto по той же причине — знают косяки своего компилятора и написали goto чтобы сгенерировался более оптимальный код
* Множество макросов так же делают библиотеку юзабельной для разных версий стандарта. Так libstdc++ работает на флагах -std=(gnu++98,gnu++03,gnu++11,gnu++14,gnu++1z,c++98,c++03,c++11,c++14,c++1z), без макросов напободие _GLIBCXX_NOEXCEPT просто не обойтись.
* Есть макросы, которые позволяют работать без RTTI (так любят делать embedded разработчики)
* Другие странности форматирования (например после открывающей фигурной скобки отступ в -4 пробела) связаны с очень древними кодовыми базами… когда мониторы были узкие и вся С++ строчка иначе на экран не помещалась
* Куча плясок с бубном вокруг аллокаторов (поэтому много всякого метапрограммирования и специфичные typedef для указателей, вместо T*). Все для того, чтобы пользовательские аллокаторы с using pointer = fancy-pointers работали
* Куча плясок с бубном вокруг точек кастомизации — поэтому часть методов помечены как ::std::, другие методы вызываются без указания namespace
* Много плясок с бубном вокруг исключений — практически всё что пользователь передал в качестве шаблонного параметра может кинуть исключение в любой момент. После такого исключения надо оставаться в валидном состоянии (да, даже если функция хеширования кинула исключения — надо подчистить ресурсы и остаться в валидном состоянии).
* Некоторые имплементации поддерживают дебажные версии стандартной библиотеки, из-за чего макросов становится еще больше, а странность кода возрастает в угоду производительности в дебажном режиме
*… (я что-то наверняка забыл)
Понятно, что скорее всего для совместимости с разными стандартами языка и разными моделями исключений, но код ужасен.
Понятно, что байка. Но когда заглядываешь в исходники некоторых реализаций STL в нее начинаешь верить :)
Тем не менее, об STL-е нужно судить не по коду самого STL (там вполне ожидаемо буде хардкор, непонятный половине действующих C++ников), а по коду, который использует STL.
Тот момент, когда сначала заминусили за мнение, а потом залайкали за конкретный пример...
http://cpp.com.ru/meyers/ch7.html
В глаза бросается упоминание шаблона std::_Tree. В Стандарте ничего не сказано о шаблоне с именем Tree, но мы помним, что имена, начинающиеся с символа подчеркивания и прописной буквы, зарезервированы для авторов реализаций. Перед нами один из внутренних шаблонов, используемых в реализации некой составляющей STL.
Оказывается, практически во всех реализациях STL стандартные ассоциативные контейнеры (set, multiset, map и multimap) строятся на основе базовых шаблонов. По аналогии с тем, как при использовании string в диагностике упоминается тип basic_string, при работе со стандартными ассоциативными контейнерами часто выдаются сообщения с упоминанием базовых шаблонов. В данном примере этот шаблон называется _Tree, но в других известных мне реализациях встречались имена tree и _rb_tree, причем в последнем имени отражен факт использования красно-черных (Red-Black) деревьев, самой распространенной разновидности сбалансированных деревьев, встречающейся в реализациях STL.
Он классный (нет)
iterator _Insert_n(const_iterator _Where,
size_type _Count, const value_type& _Val)
{ // insert _Count * _Val at _Where
#if _ITERATOR_DEBUG_LEVEL == 2
if (_VICONT(_Where) != this
|| _VIPTR(_Where) < this->_Myfirst
|| this->_Mylast < _VIPTR(_Where))
_DEBUG_ERROR("vector insert iterator outside range");
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
size_type _Off = _VIPTR(_Where) - this->_Myfirst;
if (_Count == 0)
;
else if (_Unused_capacity() < _Count)
{ // not enough room, reallocate
if (max_size() - size() < _Count)
_Xlen(); // result too long
size_type _Capacity = _Grow_to(size() + _Count);
pointer _Newvec = this->_Getal().allocate(_Capacity);
size_type _Whereoff = _VIPTR(_Where) - this->_Myfirst;
int _Ncopied = 0;
_TRY_BEGIN
_Ufill(_Newvec + _Whereoff, _Count,
_STD addressof(_Val)); // add new stuff
++_Ncopied;
_Umove(this->_Myfirst, _VIPTR(_Where),
_Newvec); // copy prefix
++_Ncopied;
_Umove(_VIPTR(_Where), this->_Mylast,
_Newvec + (_Whereoff + _Count)); // copy suffix
_CATCH_ALL
if (1 < _Ncopied)
_Destroy(_Newvec, _Newvec + _Whereoff);
if (0 < _Ncopied)
_Destroy(_Newvec + _Whereoff, _Newvec + _Whereoff + _Count);
this->_Getal().deallocate(_Newvec, _Capacity);
_RERAISE;
_CATCH_END
_Count += size();
if (this->_Myfirst != pointer())
{ // destroy and deallocate old array
_Destroy(this->_Myfirst, this->_Mylast);
this->_Getal().deallocate(this->_Myfirst,
this->_Myend - this->_Myfirst);
}
this->_Orphan_all();
this->_Myend = _Newvec + _Capacity;
this->_Mylast = _Newvec + _Count;
this->_Myfirst = _Newvec;
}
else if ((size_type)(this->_Mylast - _VIPTR(_Where))
< _Count)
{ // new stuff spills off end
value_type _Tmp = _Val; // in case _Val is in sequence
_Umove(_VIPTR(_Where), this->_Mylast,
_VIPTR(_Where) + _Count); // copy suffix
_TRY_BEGIN
_Ufill(this->_Mylast,
_Count - (this->_Mylast - _VIPTR(_Where)),
_STD addressof(_Tmp)); // insert new stuff off end
_CATCH_ALL
_Destroy(_VIPTR(_Where) + _Count,
this->_Mylast + _Count);
_RERAISE;
_CATCH_END
this->_Mylast += _Count;
_Orphan_range(_VIPTR(_Where), this->_Mylast);
_STD fill(_VIPTR(_Where), this->_Mylast - _Count,
_Tmp); // insert up to old end
}
else
{ // new stuff can all be assigned
value_type _Tmp = _Val; // in case _Val is in sequence
pointer _Oldend = this->_Mylast;
this->_Mylast = _Umove(_Oldend - _Count, _Oldend,
this->_Mylast); // copy suffix
_Orphan_range(_VIPTR(_Where), this->_Mylast);
_Copy_backward(_VIPTR(_Where), _Oldend - _Count,
_Oldend); // copy hole
_STD fill(_VIPTR(_Where),
_VIPTR(_Where) + _Count, _Tmp); // insert into hole
}
return (begin() + _Off);
}
Ну кроме микрософтовского есть еще и другие, например, libc++. Но в целом причина тут в том, что метапрограммирование на шаблонах родилось весьма стихийно, по просту говоря, создавали их для более простых вещей, а уже в процессе поняли, что их можно использовать куда хитрее, а дальше понеслась.
Хорошая новость в том, что не обязательно в своем коде писать так-же.
те, кто не знает, как должен выглядеть нормальный читаемый код, могут минусовать дальше. я переживу.
Я считаю, что именно по прихоти безумцев. Человек в своем уме не мог принять такой кодестайл и именование входных параметров и переменных. Читать его не приходится ровно до первого мисюза какой-нибудь обычной функции. Дальше вываливается пару килобайт ошибок в консоль, которые прочитать невозможно из-за дикого именования, потом я иду смотреть код… И мне приходится его читать!
Дальше вываливается пару килобайт ошибок в консоль
Во-первых, метод поиска ошибок через чтение кода стандартной библиотеки наименее эффективен независимо от её кода. Ошибка всё-таки у вас, а не там. Во-вторых, пользуйтесь нормальной IDE. В-третьих, грядут концепты, а с ними диагностические сообщения проще. И, последнее, вы приводите код из майкрософтовской реализации и критикуете язык с++ что этот код плохо написан?
пользуйтесь нормальной IDE
Какую-такую "нормальную IDE" рекомендует использовать коммитет стандартизации с++? Может они начнут нам рассказывать, какими инструментами нам пользоваться?
И, последнее, вы приводите код из майкрософтовской реализации и критикуете язык с++ что этот код плохо написан?
т.е. вот этот код лучше?
Какую-такую «нормальную IDE» рекомендует использовать коммитет стандартизации с++?
При чем тут комитет? Нормальную IDE вам советую я. Причем даже не какую-то определенную
т.е. вот этот код лучше?
да
</irony>
Именование переменных такое специально, чтобы их имена не могли пересекаться с вашими. Это даже в стандарте прописано. Сами авторы реализаций постоянно над этим иронизируют, и даже термин придумали — "mandatory uglification". Другой источник "красоты" — это поддержка всевозможных экзотических платформ и доисторических версий, из-за чего количество макросов зашкаливает.
И да, читать код стандартной библиотеки просто не надо. Достаточно cppreference.com.
Чтобы защититься от вещей вроде #define x
.
Из неочевидных вещей, от которых стандартной библиотеке приходится защищаться, это например, перегрузка операторов "&
" и ",
"
#define count int // В фирме Одуванчик принято макросы писать с маленькой буквы
struct vector {
vector(size_t count); // Боль...
};
Его не придётся читать как только добавят концепты в основные компиляторы.
Помню пытался выяснить почему в GCC STL unordered_map не желает принимать incomplete types при декларации. Так и не понял — адская смесь из разных наследующихся друг от друга и от соседского Шарика классов. И без единого комментария — что особо убивает.
Так это говорит о вашей квалификации, вы не смогли решить задачу, а вместо этого нашли виноватого, библиотеку stl, которую используют миллионы людей
Они страдают молча
Нет, они прочитали документацию на stl, знают для каких задач какой контейнер и просто решают свои задачи. Они не занимаются отладкой stl и не смотрят его код, документации на stl для них достаточно, эти люди очень ценят своё время и заинтересованы не в процессе, а в результате.
Дайте пожалуйста ссылку на место в документации, где сказано про поддержку incomplete types в различных контейнерах.
An incomplete type T may be used when instantiating forward_list if the allocator satisfies the allocator completeness requirements 17.5.3.5.1
…
An incomplete type T may be used when instantiating list if the allocator satisfies the allocator completeness requirements 17.5.3.5.1
…
An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness requirements 17.5.3.5.1
Для map и unordered_map гарантий никаких нет. Если хотите добавить — опишите идею на https://stdcpp.ru/proposals
Спасибо, был не в курсе. К сожалению, честно купленной копии стандарта не имею. А cppreference.com в разделе про unordered_map об этом молчит.
Вот текущий черновик в удобном для просмотра виде eel.is/c++draft.
А вот тут и более старые версии и в виде html, и в pdf.
Я-то задачу решил — использовав обычный мэп. Я про то, что код нынешних реализаций STL крайне тяжело читать. Что является проблемой, когда надо выяснить подробности такого вот implementation-defined behavior. Которым пестрит половина стандарта.
пример, возможно, неточный, пару недель назад наткнулся —
unordered_map<const string, string> прекрасно компилируется в GCC 4.9, но падает с ошибкой в Clang 8 (Apple).
Из-за разной реализации STL.
Когда я смотрел на код библиотеки std, мне всегда казалось, что его писали инопланетяне.… я никогда не использую в своих проектах код, который выглядит ужасно
А что вы используете в своих проектах вместо стандартной библиотеки?
код библиотеки std, мне всегда казалось, что его писали инопланетяне.… никогда не использую в своих проектах код, который выглядит ужасно
А что вы используете вместо стандартной библиотеки (вместо std::vector, std::unordered_map и std::string например)?
пишу сам
У меня для вас плохие новости: вам не понравится. Свой string я писал последний раз в 2006. Те, кто минусует, должны еще раз прочитать фразу "в своих проектах". Это не то же самое, что "везде, где только можно".
вот вам string, вот вам парсер xml, вот вам arraylist. Этому коду 10 лет, так не думайте, что сейчас я пишу так же.
если выскочит исключение по выделению памяти, то программа так и так не жилец.
вам оно надо? покажите мне свой код, и я найду в нем 1000 мест, где что-то может пойти не так. с++ не годится для того, чтобы писать безопасные программы, это давно пора понять.
наверное это мне пригодилось бы вот здесь
https://github.com/Evil-Spirit/AnandamideAPI/blob/master/include/AnandamideTypeInfo.h
Ну и приведите мне приложения, где это РЕАЛЬНО обоснованная трата бюджета разработки? Я делал движок для игрушечек, кого волнует его работоспособность в случае, если не хватило памяти?
Браузер и любые документо-ориентированные приложения. Мы можем закрыть один документ или вкладку, но остальные мы хотим сохранить.
- Серверные и любые запросо- или транзакционно-ориентированные приложения. Мы выдадим ошибку в ответ на запрос, вызвавший переполнение памяти, и сервер продолжит работать с остальными запросами.
Собственно, там в презентации автор приводит много разных примеров, и положительных и отрицательных.
Ну они точно не обрабатывают ошибки выделения памяти в каждом new
. Скорее, используют собственные аллокаторы.
- STL allocators: https://en.wikipedia.org/wiki/Allocator_(C%2B%2B)
- переопределение
operator new
/operator delete
/operator delete[]
с использованием нестандартного менеджера памяти, который работает быстрее за счет увеличения расхода памяти/отказа от многопоточности/использования специфичных для приложения паттернов работы с памятью - https://ru.wikipedia.org/wiki/Объектный_пул приложение преаллоцирует возможное количество "тяжелых" объектов и пытается работать с тем, что есть.
В контексте обсуждения, это, скорее, п.3
В вашей строке метод format не реентрабельный (им нельзя пользоваться из разных потоков одновременно).
очевидно же, что мне это не требовалось
а еще есть такое. Это писалось, когда еще не было varidatic templates, не скажу, что это читаемый код, но уровень жести зашкаливает по сравнению с std, где обычный класс std::vector выглядит, как будто по ошибке открыл бинарик.
быть const — корректным
быть exception — корректным
оптимальным (noexcept, move, минимум аллокаций)
устойчивым к #define'ам
удовлетворять требования максимальной алгоритмической сложности для всех методов
собираться на всех актуальных компиляторах
собираться при неполной поддержке стандартов
(при работе с памятью) поддерживать кастомные аллокаторы
Скольким из этих требований удовлетворяет ваш код?
Вы разве не умеете читать? Разве я такое утверждал? Разве я говорил, что мой код идеален и его можно использовать везде? Я лишь сказал, что в своих проектах я не использую код, который выглядит ужасно. И который содержит все эти ИЗЛИШКИ, про которые вы только что написали. В чем я не прав? Что вы мне пытаетесь доказать? То, что я и без вас знаю?
Да, шаблоны предоставляют возможность записать решение задачи в общем виде. Но является ли этот подход единственным для реализации стандартной библиотеки? И, конечно, очень важно понять, что должно быть частью языка, а что должно, действительно, быть реализовано в виде библиотеки. Шаблоны занимают здесь некое промежуточное положение: «ни нашим, ни вашим». Между тем, хотелось бы иметь явную поддержку со стороны языка, чтобы семантика выражений всегда гарантировала определённый результат, а программист мог бы выбирать как модель памяти, так и набор используемых им алгоритмов.
Но является ли этот подход единственным для реализации стандартной библиотеки?
Нет, если тянуть информацию о типах в рантайм или писать на макросах.
Между тем, хотелось бы иметь явную поддержку со стороны языка
поддержку чего?
чтобы семантика выражений всегда гарантировала определённый результат
а можно пример, где это не выполняется и где стандарт не помечает поведение как id/ub? Sequence points, емнип, в процессе, можете о них не упоминать
а программист мог бы выбирать как модель памяти, так и набор используемых им алгоритмов.
Модель памяти можно выбирать — используем свои аллокаторы. Какие алгоритмы вы имеете в виду?
std::vector
— это шаблон или это встроенный тип? Дело в том, что от языка программирования ожидаешь, что в нём самом реализованы основные вычислительные концепции. Кажется, что так должно быть. Или, например, сортировка.Сортировка, как таковая, единственна, но есть множество алгоритмов сортировки. Что с этим делать? В STL мы, просто, вызваем нужный нам алгоритм и всё, так? А если мы хотим иметь пул методов и выбирать по ситуации, какой лучше? По моему, это — увод мною разговора в другую сторону, но я ещё подумаю, что именно меня интересует. P.S. Ещё вспоминаю Borland C++ 5.0, где, вроде бы, были всякие контейнеры, но без шаблонов. Чем так страшно предоставление информации о типах в рантайме? И что будет, если попробовать разобраться в различных способах построения контейнерных типов? Вряд ли такой способ единственен…
Хорошо. А можно узнать, что такое
std::vector
— это шаблон или это встроенный тип?
есть простое эмпирическое правило. Видите std:: и не знаете что это — см. документацию.
Сортировка, как таковая, единственна, но есть множество алгоритмов сортировки.
Вам стандарт дает гарантию, что std::sort выполнится за
O(N·log(N)), where N = std::distance(first, last) comparisons.
(since C++11)
Быстрее, насколько я знаю, не придумали. Если вам нужен какой-то особый алгоритм сортировки, то вам его писать и так и эдак.
P.S. Ещё вспоминаю Borland C++ 5.0, где, вроде бы, были всякие контейнеры, но без шаблонов. Чем так страшно предоставление информации о типах в рантайме?
производительность ниже в несколько раз
Вообще говоря, для некоторых частных случаев, быстрее придумали.
(Есть алгоритмы сортировки быстрее встроенной в ст. библиотеку).
Например, поразрядная сортировка N K-битных чисел работает
за O(NK), вместо O(NK log(N)) при использовании std::sort().
Но согласен, что надо просто улучшать библиотеку, чтобы она сама выбирала
лучший вариант.
C++ and The Perils of Double-Checked Locking: Part I
C++ and The Perils of Double-Checked Locking: Part II
В которой рассматривается написание потокобезопасеного велосинглтона на С++
Хотя в С++11 нам упростили жизнь.
Кстати, а чем не устроил uint8_t из cstdint? И ещё — понятно, что таких случаев уже не найти, но обычно полагают что байт всё-таки может быть не восьмибитным на некоторых платформах; std::byte будет гарантировать 8, или он тоже будет формально назван платформенно-зависимым?
а чем не устроил uint8_t из cstdint
Тем что это unsigned char и соответственно is_same<uint8_t, unsigned char>::value == true.
И да, байт может быть не восьмибитным, и такие машины еще есть и работают. В std::byte столько же бит сколько бит в байте на целевой машине.
std::is_same<int, int>::value // true
std::is_same<int, unsigned int>::value // false
std::is_same<int, signed int>::value // true
std::is_same<std::byte, char>::value // false
std::is_same<std::byte, unsigned char>::value // false
std::is_same<std::byte, signed char>::value // false
std::byte — это отдельный не арифметический тип данных.
Скорее всего на такой платформе не будет uint8_t, uint16_t, uint32_t, uint64_t. Стандарт С++ говорит что эти алиасы — опциональные.
uint_fast8_t
вёл себя как int
, а не как char
, и uint_fast8_t num = 17;
std::cout << num << std::endl;
не приводило к консольному апокалипсису?Странно, потому что sizeof обязан выдавать размер в char, то есть в данном случае в 16-битных словах, а не в байтах. На TMS320C40 например, все типы 32-разрядные, sizeof(char) == 1 но CHAR_BITS == 32
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66110
std::function d{ [](int i) { std::cout << i; } };
// или
std::function d( [](int i) { std::cout << i; } );
namespace std {
enum codecvt_mode {
consume_header = 4,
generate_header = 2,
little_endian = 1
};
template<class Elem, unsigned long Maxcode = 0x10ffff,
codecvt_mode Mode = (codecvt_mode)0>
class codecvt_utf8
: public codecvt<Elem, char, mbstate_t> {
public:
explicit codecvt_utf8(size_t refs = 0);
~codecvt_utf8();
};
template<class Elem, unsigned long Maxcode = 0x10ffff,
codecvt_mode Mode = (codecvt_mode)0>
class codecvt_utf16
: public codecvt<Elem, char, mbstate_t> {
public:
explicit codecvt_utf16(size_t refs = 0);
~codecvt_utf16();
};
template<class Elem, unsigned long Maxcode = 0x10ffff,
codecvt_mode Mode = (codecvt_mode)0>
class codecvt_utf8_utf16
: public codecvt<Elem, char, mbstate_t> {
public:
explicit codecvt_utf8_utf16(size_t refs = 0);
~codecvt_utf8_utf16();
};
}
Что это могло быть?
1) полноценные модули (видел в обсуждениях)
2) библиотека модулей (вытекает из первого). сейчас приходится рыскать на просторах интернета и перебирать библиотеки от посторонних авторов на посторонних ресурсах. Хотя в С++20 заявляны, возможно, некоторые классы. Побольше их на все случаи жизни.
3) функции типа GetHelpFunction(), которая предоставит инфу по функционалу. Желательно, на универсальном языке, понятном человеку. И тут возникает проблема — пока универсального языка не существует, есть варианты костылирования из уже существующих языков, типа того же английского (недостаток — его надо знать заранее и хорошо).
Поставил сверхсложные задачи (особенно третью). Т.к. я технарь, сложностей не боюсь — уже продумываю решение проблемы. Вариант универсального языка:
обучение в играх. Там вообще может не использоваться текст, только жесты и демонстрация к чему жест приводит. Чем не универсальный язык? Но он реализуется, скорее, средствами IDE, нежели средствами самого языка программирования. Но при таком подходе от текстового языка программирования можно отказаться (он используется, но не виден пользователю).
И тут возникает проблема — пока универсального языка не существует, есть варианты костылирования из уже существующих языков, типа того же английского (недостаток — его надо знать заранее и хорошо).
По сути что бы не приняли за универсальный язык, его тоже нужно будет знать.
И еще не совсем понятно, что «хуже»: какие-то знания, или обучение.
Под библиотекой модулей — вы подразумеваете пакетный менеджер, по типу npm, pip, nuget итп? Если да, то я просто мечтаю о такой фиче. Реализуется сейчас частично отдельными независимыми разработчиками инструментов, так, например, в VS есть "NuGet для C++"
Последнее непонятно, зачем функция справки на уровне языка?
На мой взгляд самый логичный вариант — использование метаданных, как в C#, с описанием функции, аргументов итп. На английском.
Имхо, удобнее, когда управление зависимостями делается без необходимости куда-то загружать пакет или его описание. Например, когда зависимости разруливаются через ExternalProject_Add в CMake. Но только в более человеческом виде :) Используем нечто подобное уже около года — очень удобно, по крайней мере для подключения OpenSource проектов, которые можно забирать с github, bitbucket или sourceforge.
Это было нестандартное расширение в MS STL
Куча всего по constexpr вводят, даже обсуждают constexpr аллокации в куче. А хорошего метода для отладки шаблонного кода (constexpr кода в чуть меньшей степени) нету, помимо топорного static_assert и type_traits (хорошо, что ещё появился if constexpr).
Было бы здорово, если бы появилось что-то по типу static_warning. Оно, в сочетании с будущим $reflect (который, насколько я знаю, будет способен вытащить имя типа с присутствующими квалификаторами) могло бы чуточку поспособить отладке и пониманию кода, который разворачивается в момент компиляции.
А можно ли оставлять вопросы на сайте?
Концепты с двусторонними последствиями (т.е. бьют по рукам и вызывающий код, и за непредусмотренный юз в теле ф-ции). Мечты, мечты...
Вопрос насчёт Number TS: правильно ли я понимаю, что мы будем иметь эффективную длинную арифметику из коробки?
Используете ли вы в компании, или в своих личных проектах, плагины/динамическую-загрузку-библиотек? Опишите в комментариях, какими функциями и классами вы для этого пользуетесь?
Да, используем. В проекте плагинная система построена на базе CTK Plugin Framework.
Да, плагины, динамические библиотеки использовались как в своих, так и в рабочих проектах компаний. Ранее для этого были свои обертки над функциями WinAPI или POSIX. Как правило, это были кроссплатформенные обертки с реализаций под Win/Nix.
Не так давно в boost все же появилась поддержка динамических библиотек. Теперь использую ее.
Не по опросу ...
Возможно эта информация прошла мимо, но как мне могло показаться, в C++17 опять не вошла рефлексия. Эта тема для меня очень интересна. По моему мнению, появление рефлексии в C++ дало бы много возможностей для разработки прикладных библиотек без «костылей», с помощью которых можно было бы сделать компактно сервиализацию, работу с БД (ORM), реализацию RPC и разных REST API.
Если бы только рефлексия. По которой пока нет единого мнения, есть ряд прототипов. Не вошла целая пачка практически готовых и очень нужных вещей. Зато вошла зета-функция Римана и её товарки, блин.
Я бы хотел доработок по части SIMD и выровненных данных (до сих пор какие-то танцы с бубном). Ещё доработанные корутины, а не то что сейчас есть (лучше пусть просто дадут стандартизированный способ сохранить стэк, дальше сами разберёмся).
Новые форматы для чисел кому-нибудь пригодятся, но очередной контейнер со скрытыми аллокациями — гадость. Хватит лепить из C++ подобие Java! Уже столько наплодили вредных сущностей для «программистов на пол-ставки» из разряда «лепи не думая, рантайм сам с памятью разберётся». std::string, std::vector, std::function… Теперь у нас даже числовые типы будут память выделять!
Преимущество C++ в предсказуемой производительности, а эти фантазёры всё пытаются догнать Java, C# и прочие «удобные» языки.
Вы сможете сказать, будет тут выделена память или нет? Не сможете, потому что это а) зависит от внутреннего состояния vec б) зависит от конкретной стратегии std:vector на данной платформе.
Это нормально в языках со сборкой мусора, но неприемлимо в языках с «ручным» управлением памятью. Править такой код от юных (или не очень) падаванов делающий аллокации на каждый чих — удовольствие ниже среднего. И чем дальше, тем больше становится способов вызвать скрытое выделение памяти. Тот же std::function соединённый с std::bind и вроде как невинным ссылочным типом превращается… правильно, в ещё больше аллокаций.
Зато такой код писать быстро и удобно, налепил контейнеров из библиотек, в которых лежат другие контейнеры — и пошёл чай пить. Пусть авторы ОС беспокоятся о том как код работает.
Не пердёргивайте, вопрос был без написания кода. Вы всерьёз полагали что мне неизвестно как программным способом узнать состояние std:vector?
Вы смотрите в код и видите такую строчку. Будет ли выделена память? А фиг его знает, надо (как вы сами сказали) ковырять состояние объекта на этот момент.
Не надо предлагать модифицировать код, подсовывать аллокаторы и прочее. Нужно глядя на строчку всегда (как вы сами сказали) точно знать когда будет происходить выделение памяти. Сейчас вы этого не знаете, а добрые дяди из комитета гладят вас по головке и говорят что вам не нужно беспокоится о таких глупостях.
У меня не было бы претензий к std::vector если бы его не сделали «дуракаустойчивым». То есть если бы push_back() при нехватке места вызывал бы падение, а все места выделения были бы обозначены вызовами resize(). Но нееет, у нас же тут типа Java, всё должно быть «удобно» и «автоматически», чтобы даже хреновый код от студентов-первокурсников мог как-то работать.
Опять передёргиваете, вопрос был не о количестве выделенной памяти, а о самом факте выделения. В вашем примере всё очевидно. Хотя надо заметить, что даже и в таком виде в программе должно быть ограничение «сверху», тогда мы сможем сказать «здесь точно выделится не более MAX байт памяти».
>Это идёт против самого определения структуры данных «вектор». Вы путаете вектор (не std::vector, а структура данных «вектор») с обычным массивом.
Ничего я не путаю. Вы бы по-прежнему смогли изменять размер массива как и раньше.
>Тогда вам пришлось бы при каждой вставке проверять размер и кол-во текущих элементов. В таком случае проще просто использовать обычный массив.
Это и так происходит каждый раз при push_back(), просто скрытно от программиста. В моём варианте программист бы сам проверял выделенный размер когда ему нужно.
Именно практика «не нужно думать о выделении памяти, умные дяди всё придумали за нас, будем просто лепить код» являтся порочной для C++. Ещё раз, это нормально для Java и C#, где является частью их дизайна и философии. Но не для C++, где фишкой языка является предсказуемость, из которой следует полная ответственность программиста за ход выполнения своей программы.
Опять передёргиваете, вопрос был не о количестве выделенной памяти, а о самом факте выделения.
Далеко не всегда выделение памяти — узкое место в программе. А там, где это действительно боттлнек (и профайлер показал вам на это), никто не мешает вам:
а. использовать собственные аллокаторы
б. резервировать память вручную
в. пользоваться обычным массивом (или std::array)
г. не пользоваться компонентами, аллоцирующими в куче
Но не для C++, где фишкой языка является предсказуемость, из которой следует полная ответственность программиста за ход выполнения своей программы.
А что, соответствующие стандарту классы как-то непредсказуемо себя ведут? Полный контроль, как я уже упомянул, имеет смысл только по результатам работы профайлера. Иначе можно год писать то, что делается за 2-3 недели
И это при том что я люблю C++. Просто у него есть одна киллер-фича по сравнению с другими языками — никаких сюрпризов при выполнении программы. Вся программа выполняется дословно так, как её задумал программист.
требует большого количества строк для выражения элементарнейших вещейМожно пример, а лучше два?
nums = map(int,open("file.txt").readlines())
(Я не про то что python лучше c++, я про то что многие вещи на питоне просто короче)
Да, для C++ можно сделать/найти библиотеку которая может также.
Да, программа отработает на целый 1% быстрее.
Да, может то что важно мне, не важно другим (я школьник)
Но вот недавно мне был нужен скрипт, который просто сгенерирует табличку
25х25.
Писать такое на плюсах — извращенство.
Писать такое на плюсах — извращенство.А кто-то обязывает делать именно так?
Я вам страшное скажу — у языков программирования есть свои области применения, вне которых от языка мало пользы. Задача программиста, как инженера, еще и в том, чтобы подобрать подходящий инструмент для своей задачи. Так что применять C++ для простых скриптов так же дико, как и Python для СУБД уровня MongoDB или ClickHouse.
Интересно было бы сравнить выразительность C++ с чем-то другим, что может использоваться в тех же нишах. Тут хорошо выглядит разве что Rust, который, сюрпрайз, разрабатывался с учетом недостатков C++.
Вот я не обязываю))
И не спорю что у разных языков свои плюсы, минусы, области применения.
Но это не меняет того, что одни и те же вещи делаются на питоне раза в 3 короче.
Но это не меняет того, что одни и те же вещи делаются на питоне раза в 3 короче.Какие вещи, можно полюбопытствовать? Покажите, пожалуйста, аналог MongoDB на Python-е с кодом в 3 раза короче.
Или речь идет только про однострочники в студенческих лабораторных?
Конечно нет "аналогов MongoDB на Python-е", писать БД на питоне — тоже извращенство.
Но если плюсы такие из себя удобные и выразительные, то почему живут и здравстувуют JavaScript/Python/Java/etc?
Поэтому когда кто-то говорит, что C++ недостаточно выразителен, хотелось бы посмотреть на альтернативы в тех же областях, где для C++ пока еще есть место.
Но если плюсы такие из себя удобные и выразительные, то почему живут и здравстувуют JavaScript/Python/Java/etc?
Но если JavaScript/Python/Java/etc такие удобные и выразительные, то почему живет и здравствует с++?
А что вы предлагаете?
Писать
if(vec.size()==vec.capacity()){
vec.resize(vec.size()*2);
}
vec.push_back(a);
вместо
vec.push_back(a);
?
Вы в любом случае не сможете проконтролировать выделение памяти, если, скажем, это web-сервер.
vec.push_back(a);
Вы сможете сказать, будет тут выделена память или нет? Не сможете, потому что это а) зависит от внутреннего состояния vec б) зависит от конкретной стратегии std:vector на данной платформе.
Если нужно чтобы элементы хранились непрерывно и:
* если вы точно знаете, сколько элементов вам нужно — используйте array/stack_vector
* если вы знаете, сколько в среднем хранится элементов — используйте small_vector/vector+reserve
* если не знаете — используйте vector
Все известные мне не интрузивные самописные решения сводятся к одному из этих пунктов.
Если это не так — опишите пожалуйста поподробнее вашу идею и вашу реализацию контейнера.
Я не увидел ничего, что может вызвать ступор и не становится понятным через пару минут чтения описания.
Почти любой практически используемый язык можно более-менее понимать даже если в первый раз его видишь.
Посмотрите на код практически любых контейнеров Java, C#, C (где контейнеры через макросы) или Rust.
@antoshkka А что с C#? Имхо 99% всей сложности языка из-за макросов существуют. А шарп 2.0 от шарпа 7.0 для пользователя отличается только незначительным сахаром. Ну там, свойства можно инициализировать не в конструкторе, а прямо при объявлении, да экстеншн-методы пописывать… Самое сложное, что есть в языке — yield и async/await, ни в какое сравнение со сложностями плюсов не идет. Да, системы усложняются, появляются фреймворки, все такое… Но сама база не меняется никогда. И это очень серьезно ограничивает сложность: нетрудно разобраться с любым бизнес-фреймворком, если каждый элемент по-отдельности понятен и не изменился с момента академической юности.
А шарп 2.0 от шарпа 7.0 для пользователя отличается только незначительным сахаром.
Кортежы, именованные поля кортежей, structured bindings, шаблоны с is, шаблонные switch… Это только некоторые нововведения 7 C#. Когда я заглядывал в код контейнеров 5 лет назад — множество ключевых слов C# я просто не знал, т.к. большинство пользователей их не пишут в повседневном коде и в институте за год их не успели объяснить.
Написать знакомому программу на C# было адом — абсолютно непонятны времена жизни объектов в лямбдах, примитивы синхронизации немного отличаются, и их приходится учить заново…
Вывод — любой язык сложен, если его не знаешь или знаешь плохо. В любом языке есть свои сложности в огромных количествах.
И все это — сахар. Именованные кортежи — чтобы не писать класс для внутренних нужд, используется очень редко (по крайней мере у нас за использование таплов в публичном апишном интерфейсе обижают ногами), is довольно бесполезная фигня и опять же — сахар, шаблонные switch — да, новая и полезная фишка, но даже не зная их устройства увидев в первый раз в жизни код с их использованием можно сразу понять, что происходит и как это работает.
Когда я заглядывал в код контейнеров 5 лет назад — множество ключевых слов C# я просто не знал, т.к. большинство пользователей их не пишут в повседневном коде и в институте за год их не успели объяснить.
Честно говоря, не представляю, какие страшные ключевые слова могли там встретить — public/readonly/get/static? Я брал за отсчет C# 2.0 как по факту первый используемый в повседневном использовании. Так вот, с тех пор (то есть более чем за 10 лет) в языке появились ключевые слова var/dynamic/nameof, и собственно всё. Ну еще linq-синтаксис которым никто не пользуются, сюда эти where/let, но они контекстные и как я уже сказал — неиспользуемые.
Все изменения языка, как и его структуру, можно изучить ну за 2-3 недели точно. После чего можно 10 лет на шарпе не писать, а потом вернуться и продолжить с того момента, где остановился.
Вывод — любой язык сложен, если его не знаешь или знаешь плохо. В любом языке есть свои сложности в огромных количествах.
Да. Вывод верный. Только вот вопрос не в том, сложен ли язык, если ты его не знаешь, а в том, насколько легко выучить язык и поддерживать знание на определенном уровне. Я вот например уверен, что шарп я — знаю. Да, я могу не знать какие-то тонкости работы стандартных классов, могу не ответить сходу на вопрос "как сделать так, чтобы вызвались 3 разных перегрузки" или еще что-нибудь в таком роде, но в целом язык — извините, но да, знаю. В тех же плюсах людей которые могут с уверенностью сказать, что знают язык — нет в принципе. Не потому, что язык сложный, а потому что макросы позволяют его менять. И поэтому если я вижу в шарпе if я ЗНАЮ, что дальше будет открывающая скобка, условие, и закрывающая скобка). В то время как на макросах можно спокойно спрятать те же скобки в этот самый макрос и получить такой вот "руби-стайл".
Это я и написал, сложность плюсов в том, что под этим названием скрывается целый класс мета-языков, и код одного "плюсовика" будет совершенно непонятен другому. С шарпами и прочими строгими языками такое невозможно. Поэтому я и хотел узнать, в чем сложность шарпа? У него немного ключевых слов, у него нет макросов, которые могли бы поменять структуру программы, раз в 2-3 года добавляется немного сахара, которые в 99% случаев интуитивно понятны (за исключением, может быть, async/await), и я просто не вижу того, что может создавать сложность. Потому что озвученные проблемы "абсолютно непонятны времена жизни объектов в лямбдах, примитивы синхронизации немного отличаются" просто от непонимания того, что в языке с GC эти вопросы неважны в общем случае и отданы на откуп компилятору, но ведь это концепция языка. Эта концепция не меняется никогда и узнав её один раз, она остается с тобой до конца жизни языка.
Если что, я не считаю шарп святым граалем и не холиварю, сравнивая его с плюсами, просто это мой основной рабочий инструмент. Но это относится и к любому другому сравнимому по мощности языком, ну пусть будет Rust. Да, встроенный синтаксис у раста один из самых сложных из всех существующих мейнстримных языков. Но его макросы работаю с AST и не позволят сделать свой язык на базе плюсов. И это — большой плюс (вернее большой минус плюсов в том, что он это позволяет). Да, это дает некоторую гибкость, но цена за это — непомерна. Именно из-за этого язык местами не похож сам на себя, код стандартной библиотеки должен учитывать пользовательские макросы и выглядеть очень странно, и именно это и создают всю знаменитую сложность плюсов: потому что макросы (да еще и с темплейтами) порождают просто целое семейство языков там, где должен был быть один.
Так я и не обещал выход, я отвечал на
Посмотрите на код практически любых контейнеров Java, C#, C (где контейнеры через макросы) или Rust.
Что это попросту не так. Ну а сточки зрения языка тут ничего не сделать, джинн выпущен из бутылки. Точно так же, как легаси в виде winapi-интерфейсов, как легаси в виде текстового обмена в *nix, как legacy-коллекции в C# (до выхода дженериков в 2.0). Я лично не клялся в верности ни одному языку программирования. И переход с С++ на тот же раст должен быть достаточно простым. Да, там есть новые концепции, которые надо выучить, но чем больше язык построен на концепциях (они же абстракции), а не на конкретной имплементации/стандартной либе/этц, тем проще им пользоваться, потому что знания не пропадают, а аккумулируются и синергируют.
Так что, хотя я и не собирался, но все же отвечу: для С++ как языка выхода особого нет, кроме как на уровне каких-то гайдлайнов запрещать пользоваться каким-то подмножеством языка. Но это не поможет понять ошибку в стандартных классах или неправильном их использовании. С точки зрения С++-программистов выход есть и еще какой — тот же Rust это и есть C++, в котором смартпойнтеры на уровень языка добавили да решили попытаться сделать автоматическую управляемую детерменированную работу с памятью вместо new/delete. Так сказать, зачем нам С++ из которого мы гайдлайнами вырежем плохой функционал, если можно получить всё на блюдечке вместе с нормальным пакетным менеджером, приятными ништяками вроде паттерн матчинга…
Так я и не обещал выход
Так а никто и не сказал, что ваш диагноз верен и что вы, действительно, правы ;)
С точки зрения С++-программистов выход есть и еще какой — тот же Rust это и есть C++
Это, может быть, выход для «С++ программистов», но совсем не выход для C++ проектов. Переписывать мегатонны готового и работающего C++ кода на Rust или какой-то другой язык просто так никто не будет. Соответственно, все упирается в то, как развивать C++, чтобы дописывать новый код на C++ и модернизировать старый код было проще, дешевле и безопаснее.
С++ сильно продвинулся в этом направлении. Даже если брать более короткий временной интервал после 2003-го года, то в языке появилось больше средств для безопасной работы:
* auto для вывода типа переменных (устраняют ошибки вроде int l = strlen(s));
* range-for (нет целого класса ошибок с выходом за рамки массивов из-за плюс-минус единички);
* лямбды (делают удобным использование стандартных алгоритмов вместо ручных циклов, которые были источниками ошибок);
* variadic templates (сильно уменьшают надобность в макросах);
* rvalue references и move-semantic (сильно упрощают, например, прием и возврат «тяжелых» значений);
* std::array вместо C-шных массивов;
* std::unique_ptr (исправляет косяки std::auto_ptr);
* auto для вывода типа возвращаемых значений и типов в полиморфных лямбдах (опять же уменьшают надобность в макросах).
Если плотно сидишь на C++11/14, а потом внезапно возвращаещься в рамки C++98/03, то очень быстро выясняешь, насколько сильно упростился язык в повседневном применении.
Ну и еще один фактор, по поводу:
Это я и написал, сложность плюсов в том, что под этим названием скрывается целый класс мета-языков, и код одного «плюсовика» будет совершенно непонятен другому.
когда на C++ пишут софт для микроконтроллеров, драйвера и куски ядер ОС, бортовое ПО, СУБД, сложные десктоп-приложения, кросс-платформенные мобильные приложения (целиком или частично), компиляторы и виртуальные машины для языков программирования, игры, ресурсоемкие расчеты и т.д., то вполне естественно, что в каждой из этих ниш используется свое подмножество C++. Так что тот факт, что C++ очень разный — это еще один фактор, который влияет на то, как быстро и в какую сторону (в какие стороны) язык развивается.
Никаких диагнозов, я отвечал на конкретную цитату :) Не более того. Остальное — мои размышления, я никого не призываю с ними соглашаться, просто такое вот мое мнение.
Это, может быть, выход для «С++ программистов», но совсем не выход для C++ проектов. Переписывать мегатонны готового и работающего C++ кода на Rust или какой-то другой язык просто так никто не будет. Соответственно, все упирается в то, как развивать C++, чтобы дописывать новый код на C++ и модернизировать старый код было проще, дешевле и безопаснее.
Никто не просит переписывать (хотя частично это тоже делают, см. ту же мозиллу), но создание новых проектов обычно препочитают делать на новых языках, особенно если они позволяют прозрачно подключить существующие библиотеки на тех же плюсах, чтобы не переизобретать велосипед и мигрировать к себе устоявшуюся экосистему. Тот же раст это позволяет.
С++ сильно продвинулся в этом направлении. Даже если брать более короткий временной интервал после 2003-го года, то в языке появилось больше средств для безопасной работы:
Я не прошу прекращать работу комитета. Просто сейчас С++ из локомотива инноваций превратился в догоняющий язык (когда уже будет фича А, когда уже будет фича Б, ...). Это не плохо и не хорошо, просто естественный процесс. А комитет упрощает боль разработчикам, которым нужно поддерживать легаси С++ (опять же имхо). Возвращаться на старую версию языка всегда неприятно, тут что С++, что не С++, разницы не вижу. Что он развивается — это очевидно, вопрос в том, сколько в нем изначально фатальных недостатков. Кто-то в них признается, кто-то предпочитает не замечать, но рано или поздно новый язык становится настолько хорош, что люди пересиливают свою инертность и начинают миграцию. Так было с С, так было с С++, так было с джавой/коболом/..., так в ближайшее время будет а растом или еще каким-нибудь еще "более лучшим" языком. Стоит ли от этого печалиться? Вряд ли, учитывая, что все языки разиваются, а переход на другой возможен только когда он прям ваще вкусный. А это значит — меньше боли нам, разработчикам.
просто такое вот мое мнениеНу так применительно к статье про новый стандарт C++ и про предложения для следующего стандарта кроме констатации «все пропало» хотелось бы услышать какие-то идеи «по спасению». Если идея в том, что «пора валить» на Rust, ну OK.
Просто сейчас С++ из локомотива инноваций превратился в догоняющий языкПросто интересно, когда это C++ был «локомотивом инноваций»?
новый язык становится настолько хорош, что люди пересиливают свою инертность и начинают миграцию. Так было с С, так было с С++, так было с джавой/коболом/...Боюсь вас огорчить, но и C, и C++, и Java и даже COBOL продолжают активнейшим образом использоваться.
Просто интересно, когда это C++ был «локомотивом инноваций»?
Когда он появился. Можно долго рассуждать на тему, что в Smalltalk'е это было за миллион лет до плюсов, остальные идеи были взяты тоже откуда-то еще, но скомпоновалось это весьма удачно именно в плюсах. И последующее их развитие, темплейты и вот это все — это и было отличное развитие.
Боюсь вас огорчить, но и C, и C++, и Java и даже COBOL продолжают активнейшим образом использоваться.
Смотря что мы имеем ввиду под "использованием". А так-то и на whitespace люди что-то пишут. По крайней мере про кобол мне страшно даже думать, что кто-то на нем еще пишет.
Можно долго рассуждать на тему, что в Smalltalk'е это было за миллион лет до плюсов, остальные идеи были взяты тоже откуда-то еще, но скомпоновалось это весьма удачно именно в плюсах.Вы прям заинтриговали. Что именно из C++ было в Smalltalk-е задолго до того как? А то я, например, все еще нахожусь под впечатлением, что C++ позаимствовал многое из Simula, ML и Ada, но отнюдь не из Smalltalk.
Смотря что мы имеем ввиду под «использованием».Наличие business-critical приложений, прекращение работы которых станет заметным для большого количества людей. Которые нуждаются в поддержке и развитии, и которые не могут по каким-то причинам быть переписаны на более современных языках программирования.
По крайней мере про кобол мне страшно даже думать, что кто-то на нем еще пишет.А вы наберитесь смелости, представьте себе такое. Может категоричности по отношению к тому же C++ станет поменьше.
Вы прям заинтриговали. Что именно из C++ было в Smalltalk-е задолго до того как? А то я, например, все еще нахожусь под впечатлением, что C++ позаимствовал многое из Simula, ML и Ada, но отнюдь не из Smalltalk.
Мне казалось, ответ очевиден.
Наличие business-critical приложений, прекращение работы которых станет заметным для большого количества людей.
Ну, так можно хоть аду считать живым ЯП. На куче мертворожденных языков написанны всякие вещи для оборонки/авиации и т.п., то есть очень редкообновляемых сфер, остановка которых и есть "бизнес-критикал". С таким определением спорить действительно не могу, по нему плюсы действительно живее всех живых, так что засим умолкаю, вы совершенно правы.
Мне казалось, ответ очевиден.
Мне кажется, вы в очередной раз ошибаетесь. Если для вас «очевидный ответ» — это ООП, то C++ позаимствовал ООП из Simula, а не из Smalltalk. Тут можно еще много чего наговорить про различия между ООП для статически типизированных ЯП и для динамически типизированных, а так же о том, почему Smalltalk имеет совсем косвенное отношение к ООП для статически-типизированных языков, а так же почему Smalltalk считают «родителем» ООП не смотря на то, что та же Simula появилась гораздо раньше. Но это здесь явно офтоп.
Ну, так можно хоть аду считать живым ЯП.
Ладно бы вы COBOL отказывались признавать живым. Но назвать Ada мертворожденной… Это сильно. От человека с таким кругозором критика C++ начинает играть новыми красками.
ADA не мертворожденная (в отличие от многих других), но считать её в 2017 году используемым яп я не собираюсь. Хотя по вашему определению она скорее весьма популярна.
У вас и по поводу C++ какое-то избирательное суждение: чего не видите, того не существует, что не популярно, то не жизнеспособно. Отсюда и выводы о том, насколько хреново все в C++ и что поэтому C++ники должны искать выход в Rust-е (как до этого в C#, а еще до этого в Java, а еще до этого в Delphi, а еще до этого в Eiffel, ...). Остается только порадоваться, что C++ развивается не смотря на мнение таких судильщиков. Ибо вы явно даже отдаленно не представляете себе, сколько кода на C++ уже написано, как долго он будет использоваться и развиваться, и сколько еще на C++ напишут до того, как тот же Rust перейдет в категорию действительно широко используемого инструмента.
Спор ни о чем. Просто у людей закостенелое отношение. Вот писал он на плюсах 10 лет, человеку уже не интересно изучать что там за монады или higher kinded polymorphism, ему бы auto и модульность получить — уже неплохо. А это определяет рынок программистов, из которых и выбирают компании. Допустим тот же раст сейчас молод, допустим через 3 года он победит все детские болезни. Итого в 2020 году не будет ни одной причины начинать новый проект на плюсах, потому что раст ничем не хуже, во многом — лучше, а весь пласт уже существующего кода просто подключается через кросс-языковые механизмы. Но люди будут их делать. Почему? А просто из-за костности мышления. Своё старое привычное болото всегда лучше. Типичный парадокс блаба.
Заметьте, про саппортинг уже существующих систем никто не говорит, переписывать огромные проекты на другой язык в большинстве случаев не имеет смысла. Но существующие проекты понемногу умирают, как и любая система, что и будет способствовать естественному вымыванию языка. Обычный прогресс, зачем его принимать в штыки — неизвестно.
Ada вполне себе живой язык, востребованный и используемый, развивающийся (последняя версия стандарта от 2012-го года)
Мы ведь уже выяснили, что понятия "востребованный" и "живой" — различаются для нас. Для меня например живой язык имеет хотя бы 1% вакансий на площадках вроде hh.ru, все остальные — эзотерика и история.
Типичный парадокс блаба.
При таком уровне аргументации остается только спросить: сколько вам лет?
Но это так, риторический вопрос, вы уже достаточно рассказали для того, чтобы понять, как относиться к вашему мнению.
А что не так с уровнем? Я расписал все: есть язык Х (не обязательно плюсы, абсолютно любой), если есть язык У который ничем не хуже и позволяет прозрачно переиспользовать функционал Х, то смысла писать на Х нет никакого. Я не понимаю этой клятвы оставаться с языком Х пока он не почит на смертном одре. Какая разница, сколько на нем софта написано для разработчика, который делает новый проект? Да никакой.
Впрочем, я вижу вам неприятно, что кто-то не разделяет ваших восторгов по поводу плюсов настолько, что вы уже на личности решили переходить, поэтому т.к. не хочу портить никому настроения предлагаю на этом и закончить. Изначально речь шла вообще о другом, а именно о том, что и шарпы, и остальные перечисленные языки намного проще в изучении, чем плюсы. Если все остальное перечисленное — мое мнение, то это — факт и цель проектирования этих языков, по поему скромному мнению — достигнутая и превзойденная.
А что не так с уровнем?
Например, продемонстрированное ранее отсутствие кругозора, категоричность и максимализм. А так же откровенные заблуждения.
Я расписал все: есть язык Х (не обязательно плюсы, абсолютно любой), если есть язык У который ничем не хуже и позволяет прозрачно переиспользовать функционал Х, то смысла писать на Х нет никакого.
Вы забываете о существующих наработках и разработках. Если для языка X есть мегабиблиотека, которая позволяет за пару рабочих дней (а то и часов) собрать решение под какую-то типовую задачу с немного различающимися условиями, то языком X будут продолжать пользоваться даже после того, как аналог появится на языке Y. Хотя появление этого самого аналога — это еще большой вопрос.
Например, появление хороших вычислительных библиотек на C++ отнюдь не мешает продолжать использовать Fortran на серьезных вычислительных задачах. А наличие для C++ того же Eigen-а отдаляет пришествие Rust-а в эту нишу на неопределенное время.
C++ мог бы использоваться в разработке ОС даже на самом низком уровне, т.к. позволяет практически тоже самое, что и С. Тем не менее, ядра Linux и FreeBSD как разрабатывались на C, так и продолжат разрабатываться. И заменить C, который еще более древний и убогий, чем C++, Rust-у здесь так просто не получится.
Изначально речь шла вообще о другом, а именно о том, что и шарпы, и остальные перечисленные языки намного проще в изучении, чем плюсы.Так а какой практический вывод из этого? Бросить заниматься C++ и уйти всем на более молодые языки, при проектировании которых использовался опыт того же C++?
Ваша проблема в том, что вы считаете такой уход не просто разумным, но и неизбежным. А для многих текущих C++ проектов это совсем не так.
Для многих — не так, а для многих — так. Каждый решает сам, что ему приятнее — использовать более удобные абстракции и потратить время на интеграцию с существующим кодом или тратить по чуть-чуть каждый при использовании выбранного ЯП. Я считаю, что первый вариант лучше не только в плане экономии в долгосрочной перспективе, но и больше отвечает потребностям разработчика (делать однотипную необременяющую работу vs написание скрипта для автоматической работы, скрипт сложнее, но написанный однажды выручает до конца).
если есть язык У который ничем не хуже и позволяет прозрачно переиспользовать функционал Х
Это не справедливо для Y = Rust и X = C++. Ну или надо уточнить, что подразумевается под "свободно переиспользовать функционал".
а весь пласт уже существующего кода просто подключается через кросс-языковые механизмы
Это не так уж просто и удобно.
И даже если исключить этот пункт, то я не уверен, что Rust так уж во всём лучше плюсов. Опять же, "побеждение детских болезней" зависит, в том числе, от популярности и разорвать этот круг удаётся не всем языкам. Если что говорю как человек, которoму удалось найти работу на расте (хвастаюсь), так что не надо про парадокс блаба. (:
Я выше написал, работа зависит от разработчиков на рынке. Если лид хочет вести его на расте, но на рынке полтора разработчика на нем, ему нафиг не нужны такие автобусные риски, лучше взять что-то более приземленное и проверенное временем. А наличие таких разработчиков зависит от того, застряли ли они на своем уровне или в свободное время повышают квалификацию — ездят по конференциям, изучают новые ЯП и так далее.
Допустим тот же раст сейчас молод, допустим через 3 года он победит все детские болезни
вы хотите сказать, что за 3 года раст достигнет большего, чем с++ за 40 лет?
а весь пласт уже существующего кода просто подключается через кросс-языковые механизмы
Во-первых, не так всё просто. Во-вторых, зачем мне учить два языка для использования в одном проекте, если мне достаточно одного?
Можно не учить. Самообразование вообще полностью на совести разработчика. Сейчас есть модное слово "хипстерство" — так вот это не самая хорошая вещь, но и застывать на одном уровне тоже не очень хорошая идея. Быть в курсе новых веяний полезно быть всегда, а если есть более подходящий инструмент для задачи не использовать его — не очень красиво.
вы хотите сказать, что за 3 года раст достигнет большего, чем с++ за 40 лет?
Раст уже давно появился — раз. Экспоненциальный рост технологий никто не отменял — два.
move-семантика, конечно, усложняет, но читать ее всё равно относительно просто.
Если же сразу учить C++, без отсылок на C, то все гораздо проще. Когда людей учат сразу использовать std::array или std::vector вместо старых массивов, когда range-for или std::for_each+лямбда используются вместо старых for(i=0;i<ASIZE(a);++i), когда из функции возвращается std::pair<bool,std::string> вместо возврата отдельно bool-а и отдельно вручную аллоцированной строки через out-параметр и т.д., и т.п., то современный C++ осваивается гораздо легче, чем тот же C++98/03.
[line.rstrip('\n') for line in open('filename')], где класс уже содержит методы почти на все случаи жизни. Qt, мне кажется, движется в этом направлении.
Да, это, быть может, не про сам язык, а библиотеки, но язык задаёт направление, собирает определённую группу людей вокруг себя. И если язык не разрешает std::list::operator[], т.к. он будет провоцировать людей на медленные действия, а предлагает писать std::advance(std::list::begin(), 5), то и либы будут писаться такие же «универсальные», но долго дорабатываемые напильником.
И если язык не разрешает std::list::operator[], т.к. он будет провоцировать людей на медленные действияБыло бы здорово в таком случае использовать условные директивы:
#define I_WANT_TO_USE_SLOW_METHODS
Как с unsafe в C# программист берет на себя ответственность за использование такого кода
Используете ли вы в компании, или в своих личных проектах, классы способные хранить числа произвольной длинны (unbounded integers) с нестандартными аллокаторами?
Очень крутое начинание, но, либо, в Стандарт переедет библиотека GMP, либо ничего не получится. К сожалению, чтобы числа стали безразмерными и эффективными на уровне языка, никакого компилятора не хватит.
Остальные задачи потрясают своей колоссальностью, но очень бы хотелось примитивную рефлексию enum и enum class.
Пример:
enum SomeState {
Undefined = -1,
AllOk = 1,
SomeStrange1,
SomeStrange2,
...
};
...
// Неинициализированное состояние -- какое угодно, хотелось бы
// что-то вроде "Undefined = -1: default", тем более слово default зарезервировано,
// в основном, для перечислений.
SomeState s;
std::cout << s << std::endl; // неизвестно что выведет
// Вывод значения сейчас:
if (s != AllOk)
std::cout << "Not correct state: " << s << std::endl;
// Выведет "Not correct state: 2"
// Хотелось бы:
if (s != AllOk)
std::cout << "Not correct state: " << std::enum_to_string(s) << std::endl;
// Пусть выводитдет "Not correct state: SomeStrange1"
...
// Чтение из бинарного потока:
ostream >> s; // если прочитано что-то, не то, в переменной будет мусор.
// Хотелось бы:
try
{
ostream >> s; // если прочитано что-то, не то, бросается исключение.
}
catch (const std::enum_exception &e)
{...}
...
// Как следствие предыдущего, кастовать:
SomeState s;
int sint = 10;
try
{
s = enum_cast<SomeState>(sint);
}
catch (const std::enum_exception &)
{....}
Надо на уровне компилятора, стандатрной библиотеки держать:
T enum_cast<T>();
class std::enum_exception;
если совсем расслабиться :default на к-л значение в перечислении.
P.S. часто слышу, что плюсы не те, так как /нет сборщика/устаревший/не следит за памятью/over 9000 причин/. Считаю, что Стандарт работает как эталон для всех остальных.
// Неинициализированное состояние -- какое угодно, хотелось бы
SomeState s;
не согласен с таким синтаксисом: пусть будет выведен мусор, что угодно и как угодно — это только объявление, на него может даже процессорная команда не потребуется, а инициализация — это уже дополнительные телодвижения, но ваше предложение с default
при таком раскладе:
SomeState s{};
уже имеет смысл, что бы там оказалось SomeState::Undefined
. Так как, по идее, сейчас там будет 0, но такого элемента в перечислении нет: http://ideone.com/XehYqs
constexpr аллокацию «в куче»
Ждем, надеемся, верим
Используете ли вы в компании, или в своих личных проектах, плагины/динамическую-загрузку-библиотек? Опишите в комментариях, какими функциями и классами вы для этого пользуетесь?
QPluginLoader.
И так смотрю версия C++14 занимает почти 1400 страниц.
operator<=>()
Оператор «хрен знает», судя по виду :)
// P.S.: В примерах выше занк '=' тоже можно не писать :)
мелкая описка :)
Будем ждать больше вкусностей в следующем обновлении :)
Не дождался я светлого будущего и ушел на Rust. Как-то мне кажется, что после С++14 плюсы куда-то не туда пошли. Если С++11 был прорывом, то дальше пошла стагнация и конца ей не видно.
Немного не согласен со стагнацией: всё-таки C++11 весьма долго принимали и прорывом он казался, по сути, на фоне предыдущего "отсутствия развития". Думаю, что ещё не все процессы устаканились и потом комитет будет работать эффективнее. Опять же, бесконечно "революционные" фичи выдавать не реально. Когда-нибудь дело дойдёт и до модулей и т.д.
Хотя я тоже "ушел на Rust", но за развитием плюсов следить интересно.
Все эти десятки бумаг по контрактам ещё будут устаканиваться долгое время, [[implies(std::nothrow)]] появится не скоро. Сокро появятся только нечто наподобие:
void push(int x, queue & q)
[[expects: !q.full()]]
[[ensures: !q.empty()]]
{
//...
[[assert: q.is_valid()]];
//...
}
Ну и дело вообще-то не в том, чтобы [[implies(std::nothrow)]] появился. А чтобы движение началось :)
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
a) можно было выставлять требования для блоков кода (например, [[expects(std::pure)]]) с соответствующей диагностикой от компилятора в случае нарушения;
b) включения этого в стандарт, чтобы это было не только в одном конкретно взятом компиляторе.
С модулями, концептами, рейнджами 3 и другими вкусностями всё ясно — их не будет минимум до С++20, а то и дольше. Хотелось бы узнать что-нибудь по другим фундаментальным вопросам
- Опакечивание. Какой-то более-менее стандартный формат описания пакета. Для начала пусть без центрального репозитория — хотя бы как в Go возможность просто стянуть репозиторий, который будет собран по своим правилам и подключен к сборке моего проекта. Без попыток вручную скрестить CMake, MSBuild, GNU Make, Autotools, BJam etc.
- Chosen nightly compiler. GCC может концепты, MSVC может модули, CLang может что-то ещё нужное (не помню). Попробовать это всё в связке нельзя по понятным причинам.
- Если такая большая проблема добавить ranges v3, есть ли шанс в обозримом будущем получить хотя бы набор iterator adaptors? Втащить Boost не всегда есть возможность.
Я понимаю что в (1) и (2) требую слишком многого. Хотя нет — С++ единственный из мейнстрима, где нет конкретно (1) и катастрофический разброд касательно (2).
* С (2) вы всегда можете помочь — допишите нехватающий функционал в GCC/Clang ;-)
* Пункт (3) ожидайте в скором времени во всех компиляторах поддерживающих концепты — ranges выпускаются в этом году в виде отдельного TS, который можно попробовать ещё до С++20.
(1) Я тихонько надеялся, что хотя бы о необходимости прототипа начинают задумываться. Впорчем, это отдельная тема для разговора.
(2) Вопрос в том, что даже фулл-тайм я не смогу написать прототипы для всего, что может войти в стандарт, для одного компилятора.
(3) Ключевое словосочетание "поддерживающих концепты". Последний раз, когда я интересовался вопросом, такое было только в отдельной экспериментальной ветке GCC. Нв других "столпах индустрии", насколько я знаю, состояние на нуле.
* Один человек не напрягаясь в свободное время может разгрести тьму ошибок, до которых у разработчика просто не доходят руки (например ищите Mikhail Maksimov в changelog Boost. 1.64)
* Некоторые в свободное время просто берут, и добавляют недостающий им функционал. Вот например вам changelog GCC, в котором есть фраза «Thanks to Daniel Krügler, Tim Shen, Edward Smith-Rowland, and Ville Voutilainen for work on the C++17 support.». Что-то мне подсказывает, что эти ребята на GCC не работают.
Я могу тут приводить примеры до вечера. При том будут и матёрые дяди-пенсионеры, и студенты, и безработные, и люди вкалывающие на двух работах. Все они не работают фулл тайм, однако заменяют собой пару десятков фулл тайм разработчиков (за что им огромное спасибо).
- Так range-v3 вы можете использовать уже сейчас. Во всех нормальных компиляторах. Концепты там эмулируются старым добрыми добрыми шаблонами и макросами. Несколько дней назад авторы синхронизировали range-v3 с ranges TS.
- Многократный break для выхода из вложенных циклов/switch:
for(int i = 0; i < 100; i++) for(int j = 0; j < 100; j++) if(some_check(i, j))break break;
- Последним в цепочке break может стоять continue — в этом случае будет продолжен соответствующий цикл.
- continue для switch:
switch(num) { case 1: do_smth(1); continue 3; // переход к выполнению case 3 case 2: do_smth(2); continue 3; // аналогично case 3: do_smth(3); break; }
Лучше break на метку, впрочем, для этого есть goto, внезапно.
A.for(int i = 0; i < 100; i++)
B.for(int j = 0; j < 100; j++)
if(some_check(i, j)) break (A);
— для выхода из двойного цикла, и
A.for(int i = 0; i < 100; i++)
B.for(int j = 0; j < 100; j++)
if(some_check(i, j)) break (B);
— для выхода только из вложенного цикла?
Обычно в таком случае пишут функцию, которая делает return. Тот же goto, но за него камнями не закидают, да и как завещал классик Мартин — довольно модульно.
Насколько я помню, был большой спор про UFCS — возможность вызывать свободные функции как методы классов, но передавая инстанс первым аргументом. Как с этим дела?
Мне, к примеру, пришёл в голову такой вариант:
a |> f(b, c)
// превращается в
f(a, b, c)
т.е. через отдельный оператор. Хотя их, наверное, и так хватает.
И тут возникнет важный вопрос: что должно стать органической частью нового языка программирования (на уровне ключевых слов и синтаксических конструкций), а что должно быть реализовано в виде поставляемых библиотек. Беда большинства языков программирования, как раз, в том и заключается, что происходит смешение, когда на одном и том же уровне организации оказывается и код приложения, и код реализации функций и компонентов, используемых в приложении.
Например, на низком уровне находятся такие понятия как типы хранимых данных (символы, числа, строки, массивы, структуры...), на более высоком — домены (описывающие поля хранимых в базах данных объектов, которые, с одной стороны, используют для физического хранения и, возможно, для визуального представления какой-либо тип данных, а, с другой стороны, связаны с некоторой сущностью, принадлежащей концептуальной схеме базы данных), и на самом высоком уровне — уже сами сущности. Было бы крайне любопытно взглянуть на язык программирования, где реализована многоуровневая архитектура управления данными приложения…
Я не говорю ещё о визуальных компонентах и операционной системе.
Если язык программирования способен предложить программисту удобный способ привязки программных структур и визуальных компонентов, позволяющий оптимальным образом разделить, собственно, функции вывода на экран и получения сообщений от пользователя, от той задачи, которую должен в данном приложении решает данный компонент, то есть, будет предложен подлинный компонентный подход (когда код изначально создаётся в виде компонентов и специальный слой осуществляет отображение этих компонентов на представления, использующие уже визуальные компоненты, являющиеся частью операционной системы), то, я думаю, всем станет немного проще.
Numbers TS
Было бы очень хорошо! Всегда пытаешься «сварганить» нечто похожее доморощенными средствами. Но! Всё это потребует существенной «ломки» синтаксиса. Я бы мечтал о том, чтобы была возможность перегружать ",", "(", ")", ":" и многое другое, что позволило бы описывать прямо в коде конструкции произвольной сложности. Или всё это уже реализовано?
И можно ли в C++ делать что-то по типу оператора
with
? Например, так:object{
.Open();
.Load();
.Close();
}
Что я ещё пропустил (из предыдущих серий)?
P.S. А ещё свойства (properties)! Есть ли какие-либо планы относительно свойств?
Я имею в виду конструкцию, которая восходит ещё к Delpi:
__property AnsiString Name = { read=FName, write=SetName };
С этим элементом языка что-то не так?
А еще они не сокращают написание кода, делают его чтение сложнее, являются синтаксическим сахаром (к которому у меня необоснованная ненависть после Perl)… А ещё некоторые разработчики языков, где уже есть свойства, называют их «одной из ошибок при дизайне языка» и жалеют что в своё время их добавили (можете поискать по интернету ссылки).
Поискал, не нашел. В чем же беда свойств? В шарпе они хорошо себя зарекомендовали, их постоянно разивают, не бросают как Legacy. У них же куча плюсов: их можно объявляь в интерфейсе (сверхполезнаа фича), на них можно вешать валидацию… Мне скорее кажется, что концепция полей не нужна — пусть компилятор сам имплементирует свойства самого низкого уровня как поля. А то получается путаница как в уже упомянутом шарпе: есть поля, есть свойства, в рефлексии они выглядят по-разному и т.п. Была бы одна сущность (с возможностями свойств) — было бы легче.
__declspec(property)
наше всё. Я даже сподвиг ребят из ReSharper C++ добавить генераторы для этого. Но хочется чтобы это все-таки вошло как часть языка.Я бы мечтал о том, чтобы была возможность перегружать ",", "(", ")", ":"
Возможность перегрузки оператора, есть. Авторы учебников рекомендуют этим не пользоваться и на то есть много причин.
Почему в опросе нет абсолютно никакого пункта про поддержку строковых переменных в Юникоде?
Вот уже двадцать лет, как хочется материться из-за того, что стандарты C++ полностью игнорируют UNICODE!
Есть 2 большие проблемы: 1. Это далеко не однозначная вещь, как это должно работать. Юникод гораздо сложнее чем может показаться. См. utf8everywhere.org там хорошо расписаны разные проблемы.
- Для поддержки юникода нужны довольно объемные таблицы (до мегабайт размером), которые не получится тащить на микроконтроллеры например. Поэтому поддержку юникода придётся делать опциональной.
Есть же QString из QT. Или хотя бы с Java слизать API по работе с юникодом. Оба варианта существуют больше 10 лет.
Это qt 5.8 — так себе Unicode поддерживается QString'ом, не нужно ничего с таким качеством поддержки в стандарт совать.
я не в комитете, но имхо не нужно такого в стандарте, против я.
a*b
a
b
Посмотрите на Eigen например
SPMD как раз про это. Его сильнейшая сторона — это совмещение скалярных(uniform) и SIMD(varying) данных в структурах и алгоритмах включая грамотный подход к обработке ветвлений/маскирований. Но SPMD — это язык в языке с кучей ограничений равно как и OpenMP/SIMD. Есть одно похожее решение — Intel SDLТ. Это полностью шаблонная С++ библиотека, но к сожалению проприетарная потому как опирается на мощь векторизатора в ICC. Она тоже про это же но только со стороны данных, ей нужен мощный векторизатор чтобы получить хороший код.
На самом деле в комитете обсуждается ряд предложений в Parallelism TS2 ровно на эти темы:
— Более низкоуровневый вариант — это P0214R3. С короткими векторами и операциями почти как в SPMD.
— Более высокоуровневый — это P0076R1 + P0075R1 + P0601R0. Алгоритмы + вызовы функций.
Все они сейчас в разной степени проработанности, близости к (моему) идеалу и продвижения к стандарту, но ни одно из них не вошло в C++17, что обидно и ровно на это я посетовал в своём комментарии.
В очередной раз попытался реализовать обработку ошибок через исключения. Поохал от кол-ва try-catch при размотке nested_exception. В связи с noexcept и неоднозначным отношением к исключениям, возникла дурацкая мысль. Что если return станет выражением? Тогда (псевдокод конечно):
std::result<int, MyIntError> obtainInt();
//...
auto obtainedInt = (auto __r = obtainInt(), __r.is_ok() ? __r.ok() : return __r.error() );
Поохал от кол-ва try-catch при размотке nested_exception.А можно пример того, что вы пытались сделать и что получилось? Откуда у вас эти try-catch появляются?
Что если return станет выражением?Вы хотите Rust-овские try! и ? в C++? В C++17 у вас будет возможность написать так (при должной реализации типа result<R,E>):
if(auto r = obtainInt(); r) {
r.result() ...
}
else
return r.error();
А можно пример того, что вы пытались сделать и что получилось? Откуда у вас эти try-catch появляются?
Если коротко, есть некий проект где вся обработка ошибок строится на возврате true/false либо пустого/непустого объекта. Я пытаюсь подобрать схему обработки ошибок на базе цепочки исключений, построенной через http://en.cppreference.com/w/cpp/error/nested_exception. Проблема — код для прохода по этой цепочке выглядит откровенно криво:
void ForEachNestedException(std::exception_ptr ex, FunctionRef<void (FunctionRef<void()>)> callback)
{
while(ex != nullptr)
{
std::exception_ptr next = nullptr;
try
{
std::rethrow_exception(ex);
}
catch(std::nested_exception& e)
{
next = e.nested_ptr();
}
catch(...) {} // Silence for now
try // In case callback won't swallow exception
{
callback([&] () { std::rethrow_exception(ex); });
}
catch(...) {}
ex = next;
}
}
FunctionRef здесь — аналог std::function, который только ссылается на callable.
Далее пользовательский код в месте отлова будет выглядеть как-то так:
void main()
{
try
{
// ... some useful code
}
catch(...)
{
ForEachNestedException(std::current_exception(), [&] (FunctionRef<void()> rethrow)
{
try
{
rethrow();
}
catch(ContextError& e)
{
// Process first type
}
catch(AppError& e)
{
// Process second type
}
catch(std::exception& e)
{
// Process all c++ exceptions
}
catch(...)
{
// Process leftovers
}
})
}
}
Собственно, выходит минимум 2 throw на исключение. Пока ничего лучше я не придумал.
Альтернатива — делать свой NestedException.
Вы хотите Rust-овские try! и? в C++?
По сути да
if(auto r = obtainInt(); r) { r.result() ... } else return r.error();
Это будет работать как выражение? Т.е. при наличии макры
#define Try($expr) if(auto __r = ($expr); __r) { __r.result() ... } else { return r.error(); }
можно будет
auto result = Try(obtainInt()) + 42;
?
Можете поделиться ссылкой?
На всякий случай уточняю, что описанная ерунда с раскруткой цепочки призвана применяться в месте, где ошибка показывается в окошке пользователю, а не повсеместно в коде.
Это будет работать как выражение?Нет, if в C++ — это statement, а не expression.
Касательно вашего примера с исключениями. Не суть понятно, зачем вам хранить всю цепочку исключений. Но создается ощущение, что если вам нужна именно вся цепочка ошибок, то в C++ без алгебраических типов и паттерн-матчинга вы все равно поимеете приключения. Даже если придумаете хитрый возвращаемый тип result<R,E>, где в качестве E будет выступать что-то, что способно хранить цепочку вложенных ошибок.
Да, это потребует эмуляции АТД. Но без short-circuit return в выражениях оно будет выглядеть убого. Довольно забавно, что в С++, при всей необязательности исключений (а иногда и невозможности), до сих пор нет идиоматичного способа обработки ошибок без их участия.
Но без short-circuit return в выражениях оно будет выглядеть убого.Спорный вопрос. Как по мне, так Rust-овский? только усложняет чтение кода (хотя, наверное, чтение упрощает). Не хотелось бы мне в C++ видеть код вроде
result<int, Error> some_action() {
int i = get_base()? + get_seed()?;
...
}
в котором обращение к get_base() и get_seed() может приводить к преждевременному возврату с каким-то значением Error.в котором обращение к get_base() и get_seed() может приводить к преждевременному возврату с каким-то значением Error
В модели с исключениями вы этого не увидите вообще никак, не проверив код обоих функций и перегруженного оператора сложения (если он перегружен конечно). Но давайте не будем углубляться в спор "исключения против кодов возврата". Меня лично устроил бы просто макрос примерно того вида, что я привёл в своём ответе выше.
В модели с исключениями вы этого не увидите вообще никакВ модели с исключениями меня, как правило, вообще не интересует, какое именно исключение вылетит и почему. Мне нужно знать всего лишь может ли вылететь исключение или нет. Если функция/метод/оператор не помечен как noexcept, то исключение вылететь может (т.е. может вылететь практически отовсюду, если только речь не идет про хардкорный embedded или жесткий real-time). Поэтому единственное, что меня заботит — это сохранение инвариантов и откат операций. А для этого в современном C++ есть достаточно средств.
Но у вас явно другая специфика, раз вам нужно понимать что именно выскочило.
В модели с исключениями меня, как правило, вообще не интересует, какое именно исключение вылетит и почему.
Могу поспорить, что в модели с Result-типами вас это будет тоже интересовать максимум на уровне сведения типов.
Но у вас явно другая специфика, раз вам нужно понимать что именно выскочило.
Как ни странно, у меня сейчас банальнейший виндесктоп. И мне как раз не надо особо понимать, что вылетело, пока я не соберусь это ловить. Просто не очень нравится ситуация с тем, что в стандарт накидали и исключений, и кодов возврата, но при этом не продумали нормального механизма работы ни с тем, ни с другим. Более того, стандартная библиотека не придерживается единой схемы обработки ошибок.
Другое дело, что сейчас модно и молодежно в АглТД и паттерн-матчинг. А вот этого в C++ нет и, к сожалению, вряд ли появится в ближайшие лет 5-6, а то и вообще вряд ли.
Накидали — это громко сказано.
К примеру, в iostreams ошибки репортятся через состояние объекта. filesystem имеет отдельный набор перегрузок, которые возвращают код ошибки. Т.е. такое впечатление, что со стратегией не определились до сих пор.
Другое дело, что сейчас модно и молодежно в АглТД и паттерн-матчинг.
Дело даже не в этом. Я показывал выше, что "раскрутка" цепочки исключений выглядит коряво. Ещё более коряво и многословно выглядит работа через result. Это при том, что С++ частенько используют в системном программировании, где исключения отключены.
К примеру, в iostreams ошибки репортятся через состояние объекта.Корни iostreams восходят к временам, когда исключений в C++ еще не было. Кроме того, со временем в iostreams добавили возможность включить бросание исключений в случае ошибки.
filesystem имеет отдельный набор перегрузок, которые возвращают код ошибки.А это как раз следствие того, что хотели угодить всем: и тем, кого исключения устраивают, и тем, кому исключения не нравятся.
Это при том, что С++ частенько используют в системном программировании, где исключения отключены.Для системного программирования альтернатива в виде Rust-а появилась совсем недавно. Да и присутствие в этой нише того же Rust-а пока минимальное.
На счет корявости раскрутки исключений. Есть ощущение, что коряво это выглядит так, потому, что мало кому нужно использовать исключения именно таким образом.
Для системного программирования альтернатива в виде Rust-а появилась совсем недавно. Да и присутствие в этой нише того же Rust-а пока минимальное.
Я большой фанат Rust, и именно поэтому не хотел примешивать его в эту дискуссию — чтобы не разводить холивар. Тем более, по вашим собственным словам, его присутствие в нише системного программирования минимальное.
На счет корявости раскрутки исключений. Есть ощущение, что коряво это выглядит так, потому, что мало кому нужно использовать исключения именно таким образом.
Допускаю, что я их неправильно готовлю. Но, опять же, единственный способ вытрясти информацию о вложенном исключении — перевыбросить его и тут же поймать. Плюс я хотел иметь логику "раскрутки" такой цепочки отдельно от логики обработки каждого отдельного исключения.
Тем более, по вашим собственным словам, его присутствие в нише системного программирования минимальное.На данный момент так и есть, со временем ситуация может измениться. Тогда можно будет посмотреть, насколько широко в системном программировании будут применяться сложные составные Rust-овские enum-ы.
Но, опять же, единственный способ вытрясти информацию о вложенном исключенииТут интереснее другое — зачем нужно выстраивать цепочку вложенных исключений.
Тут интереснее другое — зачем нужно выстраивать цепочку вложенных исключений.
Смотря что для вас здесь вопрос — сама цепочка или то, что она на исключениях.
Если первое — вполне полезно иметь информацию не только о том, на чём конкретно "свалилась" операция, но и что конкретно при этом происходило. Конечно, идеал — выдавать ровно одно сообщение, точно характеризующее проблему. Но до него ещё как до Луны пешком, и такой подход показался мне более подходящим для плавного перехода.
Если второе — допускаю, что это не лучшее решение. Альтернативы — использовать один объект исключения, который содержит "стек" описаний контекстов и дополняет его новыми в процессе раскрутки, когда проходит через промежуточные try-catch.
Т.е. вы бы сделали что-то вроде
class AppException: public std::exception
{
// ...
public:
void addContext(ContextData);
private:
std::exception_ptr primalCause;
std::vector<ContextData> contexts;
};
?
Ведь в месте, где вы ловите первое исключение, у вас есть не только само исключение, но и описание контекста, в котором оно возникло (например, ID запроса, параметры сбойной операции и т.д.)
В принципе да, но первичный контекст может быть всё равно слишком "низкоуровневым".
class error_visitor;
class error_case {
public:
virtual ~error_case();
virtual void apply(error_visitor &) = 0;
};
class first_error_case : public error_case {
public:
first_error_case(.../* some args*/) {...}
virtual void apply(error_visitor & v) override { v.visit(*this); }
...
};
...
class error_visitor {
public :
virtual void visit(const first_error_case &) = 0;
virtual void visit(const second_error_case &) = 0;
...
};
class app_exception : public std::runtime_error {
public :
...
const std::vector<std::unique_ptr<error_case>> & cases() const;
private :
std::vector<std::unique_ptr<error_case>> cases_;
};
Потом бы в местах, где я ловлю исключения и преобразую их в app_exception делал бы что-то вроде:void some_internal_function() {
try {
... /* some actions */
}
catch(const first_exception_type & x) {
throw app_exception(..., std::make_unique<first_error_case>(...));
}
catch(const second_exception_type &x) {
throw app_exception(..., std::make_unique<second_error_case>(...));
}
...
}
Ну и где-то на верхнем уровне, где нужно было бы с исключениями разбираться для показа их пользователю, нужно было бы сделать соответствующую реализацию error_visitor-а.Т.е. суть в том, чтобы ловить исключение там, где его ловля актуальна и где требуется его преобразование в app_exception. Затем вытаскивание максимального количества полезной информации из самого исключения и окружающего его контекста, запихивание всей этой информации в какой-то объект (наследник error_case) и передача вверх уже этого объекта, а не имеющего конкретного типа exception_ptr.
PS. Все это выглядит слишком олскульно и имеет запах пропавших нафталином паттернов от банды четырех, но в отсуствии АлгТД это вполне себе работающий подход.
Что приняли в C++17, фотография Бьярне Страуструпа и опрос для C++20