Якої продуктивності можна очікувати від c_str () std :: string? Завжди постійний час?


13

Останнім часом я робив деякі необхідні оптимізації. Одне, що я робив, - це змінити деякі ostringstreams -> sprintfs. Я sprintf'ing купу std :: рядків до масиву стилів ac, ала

char foo[500];
sprintf(foo, "%s+%s", str1.c_str(), str2.c_str());

Виявляється, реалізація std :: string :: c_str () Microsoft працює в постійний час (вона просто повертає внутрішній покажчик). Здається, що libstdc ++ робить те саме . Я розумію, що std не дає гарантій для c_str, але важко уявити інший спосіб цього зробити. Якщо, наприклад, вони скопійовані в пам'ять, вони повинні були б виділити пам'ять для буфера (залишаючи її абоненту, щоб знищити її - НЕ частина договору STL), або їм доведеться скопіювати у внутрішню статику буфер (мабуть, не безпечний для потоків, і ви не маєте гарантій на час його експлуатації). Тому просто повернення вказівника на внутрішньо підтримуваний нульовий завершений рядок представляється єдиним реалістичним рішенням.

Відповіді:


9

Якщо я пам'ятаю, стандарт дозволяє string::c_str()повернути майже все, що задовольняє:

  • Зберігання, яке є достатньо великим для вмісту рядка та закінчення NULL
  • Повинно бути дійсним, доки не stringбуде викликано неконст-член даного об'єкта

Тож на практиці це означає вказівник на внутрішнє сховище; оскільки немає можливості зовнішньо відстежувати життя повернутого покажчика. Я думаю, що ваша оптимізація може припустити, що це (малий) постійний час.

У відповідній примітці, якщо форматування рядків обмежує продуктивність; Ви можете знайти більше удачі, відклавши оцінку, поки абсолютно не знадобиться щось на зразок Boost.Phoenix .

Boost.Format Я вважаю, що відкладає форматування внутрішньо, поки не буде потрібен результат, і ви можете використовувати один і той же об'єкт формату неодноразово, не повторно розбираючи рядок формату, що, як я виявив, суттєво змінився для високочастотного ведення журналів.


2
Для реалізації може бути можливим створити новий або вторинний внутрішній буфер - достатньо великий, щоб додати нульовий термінатор. Незважаючи на те c_str, що метод const (або, принаймні, має перевантаження const - я забуваю який), це не змінює логічного значення, тому може бути причиною mutable. Це може перервати покажчики від інших викликів на c_str, за винятком того, що будь-які такі вказівники повинні посилатися на один і той же логічний рядок (тому немає нової причини для перерозподілу - вже має бути нульовий термінатор), інакше вже повинно бути вже дзвінок на не -const метод між ними.
Стів314

Якщо це дійсно дійсно, c_strвиклики можуть бути O (n) часом перерозподілу та копіювання. Але також можливо, що в стандарті є додаткові правила, про які я не знаю, що це перешкоджало б. Тому я пропоную це - виклики c_strНЕ на насправді мали в вигляді , щоб бути загальним AFAIK, тому він не може бути визнаний важливим для забезпечення вони швидко - уникнути цього додаткового байта пам'яті для нормально непотрібного нульового термінатора в stringвипадках , які ніколи не використовувати c_strможе взяли перевагу.
Steve314

Boost.Formatвнутрішньо проходить через потоки, які внутрішньо проходять через sprintfзакінчення з досить великими накладними. У документації сказано, що це приблизно в 8 разів повільніше, ніж просто sprintf. Якщо ви хочете продуктивність і безпеку типу, спробуйте Boost.Spirit.Karma.
Ян Худек

Boost.Spirit.Karmaє гарною порадою щодо продуктивності, але будьте уважні, що в ньому є зовсім інша методологія, яка може бути складною для адаптації існуючого printfстильового коду (і кодерів). Я багато в чому затримався, Boost.Formatоскільки наше введення / виведення є асинхронним; але важливим фактором є те , що я можу переконати моїх колег використовувати його послідовно ( по раніше дозволяє будь-який тип з ostream<<перевантаженням - який добре бік кроки , .c_str()дебати) У Peformance Karma номера.
рецензувати

23

У стандарті c ++ 11 (я читаю версію N 3290) у главі 21.4.7.1 йдеться про метод c_str ():

const charT* c_str() const noexcept; const charT* data() const noexcept;

Повертає: вказівник p такий, що p + i == & оператор для кожного i в [0, size ()].
Складність: постійний час.
Вимагає: Програма не повинна змінювати жодне значення, збережене в символьному масиві.

Отже, так: постійна часова складність гарантується стандартом.

Я щойно перевірив стандарт c ++ 03, і ​​він не має таких вимог, а також не говорить про складність.


8

Теоретично C ++ 03 цього не вимагає, а значить, рядок може бути масивом char, де наявність нульового термінатора додається саме в той момент, коли викликається c_str (). Це може зажадати перерозподілу (це не порушує стійкість, якщо внутрішній приватний вказівник оголошено як mutable).

C ++ 11 суворіше: він вимагає часової затримки, тому переїзд не може бути здійснено, а масив завжди повинен бути достатньо широким, щоб зберегти нуль в кінці. c_str (), сам по собі, все ще може зробити " ptr[size()]='\0'", щоб переконатися, що нуль дійсно присутній. Це не порушує стійкість масиву, оскільки діапазон [0..size())не змінюється.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.