|
Программирование >> Обобщенные обратные вызовы
> Рекомендация Никогда не используйте функции, которые пишут в буфер, размер которого не проверяется (например strcpy, sprintf), или функции, которые не завершают нулевым символом С-строку (например strncpy, basic string: :сору). Они могут привести не только к аварийному завершению работы профаммы, но и представляют собой уфозу безопасности-системы -- атака, основанная на переполнении буфера, продолжает оставаться наиболее популярной у хакеров и разработчиков вредоносных профашл. Более подробно этот вопрос поднимается в задачах 2 и 3. Все то, что делается при помощи функции сору, можно не менее просто, но гораздо более гибко сделать при помощи старого доброго алгоритма std: :сору. string s = 0123456789 ; char* bufl = new char[5]: s.copyCbufl. 0, 5); buf содержит О, V , 2, 3, 4 copyCs.begin С), s.begin()+5, bufl); buf содержит 0, 1, 2, 3, 4 int* buf2 = new int[5]; s.copy(buf2, 0, 5); Ошибка: первый параметр не char* copy(s.begin О, s.begin()+5, buf2); все в порядке: buf2 содержит значения, соответствующие символам О , 1, 2, 3, 4 (т.е. их ASCII-значения) Кстати, этот код заодно демонсфирует, как basic string::сору можно фиви-ально сделать обычной функцией, не являющейся другом класса. Проще всего сделать это при помощи алгоритма сору. Пусть это останется еще одним упражнением для читателя, надо только не забыть корректно обработать частный случай п == npos. Переведите дыхание и сделайте очередной глоток кофе. Вот еще одна простенькая функция: substr. Вспомним ее сигнатуру<°. basic string substrCsize type pos = О, size type n = npos) const; Замети.м, что substr легко реализовать как обычную функцию, не являющуюся другом класса, поскольку даже стандарт гласит, что она просто возвращает новый объект типа basic string, сконструированный как basic string<charT,traits,Anocator>(data()+pos,min(n,size()-pos)) Таким образом, создание новой Сфоки, являющейся подсфокой существующей, можно одинаково легко и просто выполнить как с применением функции substr, так и без нее, с использованием более обобщенного конструктора stri ng. string s = 0123456789 ; string s2 = s.substrC 0. 5 ); s2 содержит 01234 Проницательные читатели могут заметить, что эта функция получает параметры в порядке позиция, длина , в то время как только что рассмотренная функция сору получает такие же параметры в порядке длина, позиция . Помимо эстетического несоответствия, это может оказаться просто опасным. Попытка запомнить, в каком порядке надо передавать параметры в ту или иную функцию, может привести пользователей basic string в полный ступор, тем более что тип этих параметров одинаков, так что компилятор пропустит неверный по сути код без замечаний, ну а дальше... На мой предвзятый взгляд, такие вещи больше всего напоминают мину-растяжку на лесной тропе, на взрывателе которой выбита надпись Сделано комитетом по стандартизации С++ . Впрочем, я отвлекся... string s3( s.dataO, 5 ); s3 содержит 01234 stri ng s4( s.beginC), s.begi nC)+5 ); s4 содержит 01234 Вот и все, кофе допит, отдых закончен... Работы осталось очень немного: всего только два семейства - compare и *find*. compare в) compare Предпоследнее семейство функций - compare. В нем пять членов, и можно тривиально показать, что все они эффективно реализуются как обычные функции, не являющиеся друзьями класса. Как? В стандарте эти функции определены через функции basic string size и data (которые, как мы уже решили, являются членами класса), а также trai ts:: compare, которая и выполняет всю работу (более полую информацию о trai ts: : compare можно найти в [Josuttis99]). Это было сравнительно просто? Тогда давайте вместо сравнивания сложностей займемся их поиском... find г) Семейство find (find, find * и rfind) Увы, найти их не сложно. На закуску нам осталось семейство функций, по сравнению с которым восемь insert и десять replace - так, забавы для разминки. В классе basic string - не верите, считайте сами - 24 (да, двадцать четыре) варианта алгоритмов поиска. Все их можно разбить на шесть подсемейств, каждое из которых состоит ровно из четырех функций: find. Прямой поиск первого вхождения строки или символа (str, s или с), начиная с позиции pos; rfi nd. Обратный поиск первого вхождения строки или символа (str, s или с), начиная с позиции pos; fi nd fi rst of. Прямой поиск первого вхождения любого из одного или нескольких символов (str, s или с) из списка, начиная с позиции pos; find last of. Обратный поиск первого вхождения любого из одного или нескольких символов (str, s или с) из списка, начиная с позиции pos; fi nd fi rst not of. Прямой поиск первого вхождения любого симюла, кроме одного или нескольких указанных символов (str, s или с), начиная с позиции pos; fi nd .l ast not of. Обратный поиск первого вхождения любого символа, кроме одного или нескольких указанных симгюлов (str, s или с), начиная с позиции pos. Каждое подсемейство состоит из четырех членов: str, pos , где str содержит искомые символы (или символы, для которых осуществляется поиск несовпадения), а pos - начальная позиция в строке; ptr, pos, n , где ptr - указатель сНагт*, указывающий на буфер длины п, содержащий искомые символы (или символы, для которых осуществляется поиск несовпадения), а pos - начальная позиция в строке; ptr, pos , где ptr - указатель charT*, указывающий на буфер, завершающийся нулевым символом, который содержит искомые символы (или символы, для которых осуществляется поиск несовпадения), а pos - начальная позиция в строке; с, pos , где с - искомый символ (или символ, для которого осуществляется поиск несовпадения), а pos - начальная позиция в строке. Все эти функции могут быть эффективно реализованы как обычные функции, которые не являются друзьями класса (оставляю это читателям в качестве очередного упражнения). Добавлю еще одно замечание о поиске в строках. В де йствите л ьн ости, как вы могли заметить, кроме большой группы алгоритмов basic string: :*fincl*, стандарт С++ предоставляет пусть не столь многочисленную, но полнофункциональную группу алгоритмов std: :*find*, в частности: std: rfind может выполнять те же действия, что и basic string: rfind; std: :find с использованием reverse iterator, a также std: :find end могут выполнять те же действия, что и basic string:: rfind; std: :find fi rst of или std: :find с соответствующим предикатом могут выполнять те же действия, что и basic string: :find fi rst of; std: :find fi rst of или std:: find с соответствующим предикатом, использующие reverse iterator, могут выполнять тс же действия, что и basi c st ring: :find last of; std: :find с соответствующим предикатом может выполнять тс же действия, что и basic string:: find first not of; std: :find с соответствующим предикатом и использованием reverse, i terator может выполнять тс же действия, что и basi c stri ng:: find 1ast not of. Кроме того, эти алгоритмы более гибкие, так как работают не только со строками. По сути, все алгоритмы basic string: :*find* можно реализовать с использованием алгоритмов std::fi nd и std::find end, снабдив их при необходимости соответствующими предикатами и/или итераторами reverse iterator. Так что, может быть, можно просто обойтись без алгоритмов basi c string: :*find* и посоветовать программистам использовать вместо них существующие алгоритмы std: :find*? Можно, но осторожно: несмотря на то, что таким образом можно эмулировать все алгоритмы basi c stri ng: : find*, в ряде случаев использование для этой цели реализации по умолчанию std: rfind* может привести к значительной потере производительности. Три вида каждой функции find и rfind, которые выполняют поиск подстрок (а не отдельных символов), можно реализовать гораздо более эффективно, чем методом в лоб , когда производится проверка каждой позиции и сравнивание подстрок для каждой из них. Есть хорошо известные алгоритмы, на лету строящие для поиска подстрок (или доказательства их отсутствия) конечные автоматы, и вполне возможно их применение в реализации функций поиска. Нельзя ли воспользоваться такой оптимизацией, предоставив перегрузку (не специализацию - см. задачу 7) std г rfind*, которая работает с итераторами basi c st ring: :iterator? Да, но только если basic string::iterator является типом класса, а не обычным указателем сНагт*. Причина в том, что если бы эго был обычный указатель, то специализация std: :find для него срабатывала бы для всех без исключения указателей этого типа - что, очевидно, не верно. Она должна срабатывать только для указателей, указывающих в буфер std:: stri ng. По этой причине нам определенно нужно выделить basic string::iterator в отдельный тип, легко отличимый от других итераторов и указателей. Тогда специализированная версия может быть специально оптимизирована для максимально эффективною поиска подстрок. Резюме Декомпозиция и инкапсуляция - определенно Хорошие Веши. В частности, лучше разделять алгоритмы и контейнеры, как это сделано в стандартной библиотеке.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |