Аргумент f — это будет указатель на C-шную функцию формата: char * fn(char *a), которая при этом модифицирует значение своего аргумента. Соответственно, пихнуть в нее результат basic_string::data() просто так нельзя, т.к. возвращается const char *. А делать const_cast к этому указателю — это провоцировать UB. тыц
Мне нужно было обернуть лишь 2 функции - basename, dirname в цепепешный вариант :-) Мой шаблон имеет смысл только для этих 2-х ф-ий - отсюда и ограничение, которое было возложено на компилятор
Вообще это все можно было бы сделать без шаблонов и с ограничением доступа к деталям реализации гораздо проще.
И главная проблема здесь — это манипуляции с временным буфером для хранения мутабельной C-шной строки. А не ограничение для возможности вызова dirname или basename. Если не сильно заморачиваться на производительность, то создание этого самого временного буфера можно было бы записать вообще вот так:
Но он является показательным примером, как даже знаток таких тонкостей цепепе, как время жизни временного объекта угодил в ловушку :-) Ты использовал метод basic_string<>::data, который возвращает указатель на данные без терминирующего нуля :-) Но ведь в мане по basename(3) говорится: «The functions dirname() and basename() break a null-terminated pathname string into directory and filename components.» :-) Т.е. слажался первый раз :-) Теперь посмотрим что там в стандарте про basic_string<>:data - раздел 21.4.7.1 Requires: The program shall not alter any of the values stored in the character array. :-) Но т.к. basename/dirname таки модифицируют передаваемую им C-строку, то слажался второй раз :-)
Ну и в-третьих, это повезло, что бойлерплэйт тут можно упихнуть в одну строку, и не так очевидна необходимость в шаблоне :-) А вообще-то сама идея - использовать *шаблон*, дабы свести на нет бойлерплэйт, но такой шаблон, который имеет смысл только с определёнными аргументами :-) И в цепепе это приводит к километрам простыней :-)
PS. Т.к. пример показывает как на замечательном цепепе можно написать с виду короткий код, но который совершенно неприемлем в продакшене :-) Так что please, don't use C++ :-)
Принципиальный :-) Если алгоритм функции шаблона имеет смысл только с определёнными функциями, то логично проверить на стадии компиляции, что шаблон инстанцируется для использования только этих функций :-) Это даёт железную гарантию правильности работы :-)
Самый простой — определить process_path внутри пространства имен impl или details и объявить это пространство имен деталями реализации.
Теперь простыня с private/public, векторами с резервами :-) Ясно же, что простые вещи на цепепе делаются слишком сложно :-) А уж с вовлечением шаблонов и подавно :-)
Вы и считать не умеете? Сравните со своим вариантом, в котором еще и ошибка сидит:
Какая ошибка? :-) Бугага :-) Я то знаю, к чему ты клонишь - strdup типа не проверил на NULL :-) Наивно полагать, что после истощения памяти можно восстановиться, ибо финита ля комендия, game over :-)
Наивно полагать, что после истощения памяти можно восстановиться, ибо финита ля комендия, game over :-)
Решение об этом не должен принимать разработчик библиотечной функции path_dirname. Нет памяти для работы — выброси наверх std::bad_alloc, пусть у пользователя вашей библиотеки голова болит. Это всяко лучше, чем крах всего приложения по сегфолту из-за разыменования нулевого указателя.
Нет, в следствии этого глаза красные после цепепе :-)
Слово «нет» в этой фразе лишнее: в следствии вашей идиотии получается чрезмерно переусложненный код, в следствии чего у вас красные глаза после цепепе.
получается чрезмерно переусложненный код, в следствии чего у вас красные глаза после цепепе.
Ну так язык провоцирует писать такую муть, где либо простыни, либо вектора с нулями вручную, либо неопределённое поведение, либо инкапсуляция ф-й в структуру с применением директив доступа :-) А всего-то надо от языка - проверить строчки в компайл-тайме :-) Лол :-)
Ну что лицемерить? :-) Давай по-честному :-) Я признаю, что не проверил strdup на NULL :-) Mea culpa :-) Но кто-нибудь из присутствующих когда-нибудь проверяет на предмет std::bad_alloc? Кто-нибудь выполняет в обработчике код восстановления в ситуации тотального исчерпания виртуальной памяти? Только по-честному :-)
С++ частыми местами неудобный, криво сшитый, многокостыльный шубниггурат от семейства сиподобных. Но разруха не в клозетах, она в головах! (с) Ф.Ф. Преображенский. А производительность такого прожорливого софта изначально вопрос архитектурный.
Но кто-нибудь из присутствующих когда-нибудь проверяет на предмет std::bad_alloc?
В подавляющем случае это не нужно. bad_alloc мало чем отличается от других типов исключений.
Если программист озадачивается понятиями exception safety guarantees, то он пишет код, которые обеспечивает хотя бы базовую гарантию безопасности. Что означает активное использование RAII и автоматический откат не выполненных до конца операций (ну или хотя бы оставление внутренностей в таком состоянии, которое позволяет спокойно завершить работу). При таком подходе, не суть важно, выскакивает ли bad_alloc или invalid_argument.
Кто-нибудь выполняет в обработчике код восстановления в ситуации тотального исчерпания виртуальной памяти?
Не понятно, что вы понимаете под восстановлением.
У меня, например, специфика такая, что в приложениях куча рабочих нитей и куча независимых акторов на них. Если какому-то актору не хватило памяти, то это не повод грохать сразу все приложение. Можно грохнуть проблемного актора, в процессе чего часть ресурсов будет возвращена системе.
Другое дело, если при этих операциях исключения могут возникать вновь. Но это уже, скорее всего будет либо исключение из деструктора при раскрутке стека из-за предшествующего исключения (что сразу ведет к std::terminate), либо же будет исключение где-то в блоке catch на самом верху. И там уже вполне уместно дергать std::abort(), т.к. возможностей для отката к какой-то исходной точки нет.
Но это все гораздо лучше, что сегфолт из-за разыменования нулевого указателя.
В твоём же говнокоде в process_path можно передать любую ф-ю, а не только удовлетворяющую семантике basename/dirname :-)
А зачем ту функцию выставлять наружу? Почему в качестве интерфейса не может торчать просто path_basename и path_dirname? Тогда и не надо будет решать несуществующую проблему.
Почему в качестве интерфейса не может торчать просто path_basename и path_dirname?
Если это inline-функции, определенные в публичном заголовочном файле, то спрятать то, что они используют у себя внутри, не так-то просто. Особенно, если пытаться сделать железобетонную защиту от идиотов.
В подавляющем случае это не нужно. bad_alloc мало чем отличается от других типов исключений.
Не отловленный bad_alloc, как и любое другое необработанное исключение, приводит к завершению программы :-)
При таком подходе, не суть важно, выскакивает ли bad_alloc или invalid_argument.
Это 2 совершенно разные проблемы - логическая ошибка и отсутствие памяти :-) Логическая ошибка - это то же, что нарушение assert - т.е. ошибка, которая по определению может быть обнаружена ещё *до* запуска программы :-) bad_alloc - это ошибка времени выполнения, причём ошибка, которая говорит, что ничего уже сделать нельзя :-) А в Linux это уже как предзнаменование того, что процесс будет уничтожен OOM-killer :-)
Не понятно, что вы понимаете под восстановлением.
Чего не понятного? :-) Что делать то в случае bad_alloc, или, что тоже, какой толк проверять на NULL в strdup()/malloc()? (разве что для правильности с т.з. программирования)
Можно грохнуть проблемного актора, в процессе чего часть ресурсов будет возвращена системе.
Можно *попытаться* грохнуть :-) Только нет гарантии, что получится - на Linux твой процесс может стать жертвой OOM-killer :-)
Но это все гораздо лучше, что сегфолт из-за разыменования нулевого указателя.
С т.з. дурного тона таки лучше :-) А на практике - что при сегфолте, что при bad_alloc - исход почти неизбежно один :-)
нехватку памяти стоит ловить хотя бы для сохранения данных о состоянии софта. все ресурсы. которые понадобятся для такого сохранения выделяются заранее.
нехватку памяти стоит ловить хотя бы для сохранения данных о состоянии софта. все ресурсы. которые понадобятся для такого сохранения выделяются заранее.
Так, так, и ты делаешь это в своём софте? И твои обработчики bad_alloc гарантированно не требуют памяти для освобождения ресурсов? И ты, конечно, не на Linux? :-)
Не отловленный bad_alloc, как и любое другое необработанное исключение, приводит к завершению программы :-)
Специально для идиотов: нет смысла ловить именно bad_alloc. В местах, где программа может предпринять какие-то действия, имеет смысл ловить просто std::exception.
Чего не понятного?
Например, почему я пытаюсь объяснить что-то идиоту.
Что делать то в случае bad_alloc, или, что тоже, какой толк проверять на NULL в strdup()/malloc()?
В каком именно приложении? HTTP-сервер — это одно, Word из Office — другое, видеоконвертер третье. В каждом из этих приложений будет своя система обработки проблемных ситуаций и свои точки, в которых возможно что-то сделать.
Поэтому разговор о возможности восстановления без явного сужения предметной области не имеет смысла. И мне не понятно, почему вам это не понятно.
на Linux твой процесс может стать жертвой OOM-killer
К счастью, даже мир Unix-ов не ограничивается одним только Linux-ом.
В местах, где программа может предпринять какие-то действия, имеет смысл ловить просто std::exception.
«Какие-то действия» :-) Т.е. при возникновении std::exception, программа может делать «какие-то действия» :-) Грохнуть поток, например, при возникновении неважно какого, но исключения :-) Лол :-)
Поэтому разговор о возможности восстановления без явного сужения предметной области не имеет смысла. И мне не понятно, почему вам это не понятно.
Мне не понятно что делать в случае с bad_alloc на Linux :-) Вот и всё :-)
обработчики бэдаллоков и нуллов от маллоков гарантированно не просят выделит память в обработчике нахватки памяти. а ты не пишешь гарантированный код?
Ну если твой обработчик нехватки памяти будет располагать каким-то заранее отведённым резервом, то флаг в руки :-) Может быть не только в log запись перед завершением сумеешь сделать :-) Вопрос в другом изначально был - кто-нибудь так делает? :-)
Метод basic_string::data() возвращает константный указатель, а модификация значения по этому указателю — это UB. тыц
const charT* data() const noexcept;
Returns: A pointer p such that p + i == &operator[](i) for each i in [0,size()].
Это стандарт. Сможешь реализовать так, чтоб все сломалось при модификации? Так что это такой же UB, как и то, что до С++11 все прекрасно знали, что вектор не имеет «дырок».
я такой серьезный софт, для которого это необходимо или просто полезно пока не писал, и, возможно, так делать не буду. но это сделать можно и без извращений
К счастью, даже мир Unix-ов не ограничивается одним только Linux-ом.
Да слышал я эти сказки про множественность реализаций :-) От тех же лисперов, которые любят рассказывать про кроссплатформенность и существование разных рантаймов, ага :-) Только вот пишут в основном на SBCL :-) Спрашивается, зачем было израсходовано столько человеко-лет на около десятка реализаций, если в ходу, фактически, осталась одна? :-) Потраченное зря время, которое вместо доказывания чего-то друг перед другом можно было потратить на создание одной суперской реализации :-) Так и в мире Unix-like :-) Только в мире Unix-like сообщество значительно больше :-) А суть та же - бесполезная трата времени для одного и того же различными командами :-)
const charT* data() const noexcept; Returns: A pointer p such that p + i == &operator[](i) for each i in [0,size()]. Это стандарт.
Если вы уж взялись ссылаться на стандарт, то не останавливайтесь на полдороги:
Requires: The program shall not alter any of the values stored in the character array.
Так что UB здесь будет. Но вам никто не запрещает надеяться, что это никогда боком не выйдет (хотя сильно зависит от версии GCC, т.к. не все сидят на последних версиях).
использовал метод basic_string<>::data, который возвращает указатель на данные без терминирующего нуля :-)
Нет, в С++11 data и c_str возвращают одинаковое значение - указатель на данные с нулем.
Т.е. слажался первый раз :-)
Нет, это ты продолжаешь лажать раз за разом.
The program shall not alter any of the values stored in the character array.
Ответил выше, стандарт выписан так, что data() вернет абсолютно тот же указатель, что и &[0]. Кроме пустых строк, где data() не UB в отличие от. Так что это вполне допустимый трюк, тем более нам эта строка больше не будет не нужна, так что пусть там расставляются нули где угодно. Ну и если говорить про стандарт, то твоих strdup, basename и dirname вообще там нет.
в-третьих, это повезло, что бойлерплэйт тут можно упихнуть в одну строку
А тебе не повезло, да?
вообще-то сама идея - использовать *шаблон*, дабы свести на нет бойлерплэйт
template <class F>
string func_c_string( F f, string s ) {
return f( (char*) s.data() );
}
int main()
{
const char* s = "/please/dont/use/c++";
cout << func_c_string( dirname, s ) << '\n';
cout << func_c_string( basename, s ) << '\n';
}
Посмотрел :-) Вижу много лысых теоретиков, склонных к менторству :-)
MacOS — это же Unix, но не Linux, там ядро из FreeBSD.
Вау, и что? :-) Много ли тех, кто использует MacOS на сервере? :-) У меня нет столько времени, чтобы свой код портировать на все платформы в мире лишь потому, что они в мире есть, и что, быть может, 1 человек когда-то запустит мой код, в котором, быть может, в 21 веке, имея 16 Гб оперативы нарвётся на своём Маке на bad_alloc, и вот тут обработчик георически упасёт счастливчика от завершения приложения, использующего мою доморощенную библиотеку (что вряд-ли) :-) Лол :-)
Ну и приплетать в качестве примера кроссплатформенности давно никому не нужный Lisp — это признак большого ума, да.
Ты хоть бы выучил тот самый Lisp :-) Для ума полезно :-)
Да ладно, ладно :-) Это ты тут KISS демонстрируешь, я уже понял откуда ноги растут :-) Хороший код, компактный, но в стандарте ведь сказано, как я уже говорил, что не можно программе модифицировать данные по basic_string<>::data :-) Не могу себе позволить нарушать требования стандарта :-) И пусть даже data возвращает массив без дырок с \0 в конце - стандарт не велит его менять, вот и всё :-)
Расскажи каким образом, ты получил свой указатель через &[0], я через data(), по стандарту они равны с точностью до бита. Как ты получишь тут UB? Я вижу только один случай, где будет разница - пустые строки, тут очевидно реализация может из data() вернуть константу, и под это и выписано это предупреждение. Но пустую строку и модифицировать не получится.
А я вижу стандарт, который запрещает так делать. И, поскольку стандарт писали не дураки и данное ограничение кочует из стандарта в стандарт, то считаю, что на это есть веские причины. Даже если я сейчас не знаю, каким образом это можно нарушить.
Посему лучше сейчас лишний раз присесть, чем потом отгрести.
Ну и если говорить про стандарт, то твоих strdup, basename и dirname вообще там нет.
Нет, ну ты ваще :-)
И все.
Так ведь не всё :-) Я тоже так изначально и сделал (за исключением того, что не стал связываться с data()) :-) Но потом возникла идея продемонстрировать как сложно в цепепе заставлять делать компилятор то, что хочется :-)
У меня нет столько времени, чтобы свой код портировать на все платформы в мире лишь потому, что они в мире есть, и что, быть может, 1 человек когда-то запустит мой код
Ну понятно, вы говнякаете какую-то никому не нужную хрень. А все проблемы в C++.