char * проти std :: рядок в c ++ [закрито]


81

Коли слід використовувати std::stringі коли використовувати char*для управління масивами chars в C ++?

Здається, вам слід скористатися, char*якщо продуктивність (швидкість) є надзвичайно важливою, і ви готові прийняти деякі ризиковані справи через управління пам'яттю.

Чи слід розглянути інші сценарії?

Відповіді:


56

Ви можете передавати std :: рядки за посиланням, якщо вони великі, щоб уникнути копіювання, або вказівник на екземпляр, тому я не бачу жодної реальної переваги за допомогою покажчиків char.

Я використовую std :: string / wstring для більш-менш всього, що є фактичним текстом. char *корисний для інших типів даних, і ви можете бути впевнені, що його вивільняють як слід. В іншому випадку std :: vector - це шлях.

Імовірно, з усього цього є винятки.


8
чи є різниця в продуктивності перед двома?
vtd-xml-автор

3
@ vtd-xml-author: Деякі, можливо. Прямий char *майже не має накладних витрат. Яких саме накладних витрат std::stringя не знаю, це, ймовірно, залежить від реалізації. Навряд чи я очікую, що накладні витрати будуть набагато більшими, ніж показники голого символу. Оскільки я не володію копією стандарту, я не можу детально описати будь-які гарантії, надані стандартом. Будь-яка різниця в продуктивності, ймовірно, буде залежати від операцій, які потрібно виконати. std::string::sizeможе зберігати розмір поруч із символьними даними і, отже, швидше, ніж strlen.
Skurmedel

2
Чому б не використовувати std :: string для нетекстових даних? Вони не припиняються з нулем, тому ви зможете зберігати там все, що завгодно.
Кейсі Родармор,

1
@rodarmor Ви можете зберігати все, що завгодно, хоча це досить ризиковано, оскільки рядок призначений для символьних рядків із нульовим закінченням. Ви повинні бути обережними, використовуючи лише двійково безпечні операції, наприклад append(const string&)і append(const char*, size_t)замість operator+=().
boycy 13.03.12

6
Ти впевнений? Я знаю, що багато операцій припускатимуть, що char * - це рядок із нулем, що закінчується, але я не можу придумати жодного, що передбачає, що std :: string не містить нулів.
Кейсі Родармор

58

Моя точка зору така:

  • Ніколи не використовуйте char *, якщо ви не називаєте код "C".
  • Завжди використовуйте std :: string: Це простіше, зручніше, оптимізоване, стандартне, це заважатиме вам мати помилки, перевірено і доведено, що воно працює.

13

Використання необроблених рядків

Так, іноді ви дійсно можете це зробити. При використанні const char *, масивів char, виділених у стеку та рядкових літералах, ви можете це зробити таким чином, що взагалі не виділяється пам'ять.

Написання такого коду часто вимагає більшої продуманості та обережності, ніж використання рядка чи вектора, але за допомогою відповідних прийомів це можна зробити. За допомогою належних методів код може бути безпечним, але завжди потрібно переконатися, що під час копіювання у char [] ви маєте певні гарантії щодо довжини копіюваного рядка, або ви елегантно перевіряєте та обробляєте великі рядки. Якщо цього не зробити, це надало сімейству функцій strcpy репутацію небезпечних.

Як шаблони можуть допомогти написати безпечні буфери символів

Що стосується безпеки буферів char [], шаблони можуть допомогти, оскільки вони можуть створити інкапсуляцію для обробки розміру буфера для вас. Такі шаблони реалізовані, наприклад, корпорацією Майкрософт, щоб забезпечити безпечну заміну strcpy. Приклад тут витягнутий з мого власного коду, реальний код має набагато більше методів, але цього має бути достатньо, щоб передати основну ідею:

template <int Size>
class BString
{
  char _data[Size];

  public:
  BString()
  {
    _data[0]=0;
    // note: last character will always stay zero
    // if not, overflow occurred
    // all constructors should contain last element initialization
    // so that it can be verified during destruction
    _data[Size-1]=0;
  }
  const BString &operator = (const char *src)
  {
    strncpy(_data,src,Size-1);
    return *this;
  }

  operator const char *() const {return _data;}
};

//! overloads that make conversion of C code easier 
template <int Size>
inline const BString<Size> & strcpy(BString<Size> &dst, const char *src)
{
  return dst = src;
}

1
+1 для "При використанні const char *, масивів char, виділених у стеку та рядкових літералах, ви можете зробити це таким чином, що взагалі не виділяється пам'ять." Люди забувають, що "розподіл" стеку відбувається набагато швидше, ніж купа.
NoSenseEtAl

char*рядки не завжди в стеку. char *str = (char*)malloc(1024); str[1024] = 0;
Коул Джонсон,

@ColeJohnson Я не стверджую, що я просто хочу сказати, що якщо ви хочете, щоб ваш рядок був виділений у стек, вам потрібно використовувати const char * у поєднанні з рядковими літералами, а не std :: string.
Сума

9

Один випадок, який ви ПОВИННІ використовувати, char*а не std::stringколи вам потрібні статичні константи рядків. Причиною цього є те, що у вас немає контролю над модулями замовлення, які ініціалізують їх статичні змінні, а інший глобальний об’єкт з іншого модуля може посилатися на ваш рядок до його ініціалізації. http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Static_and_Global_Variables

std::string плюси:

  • керує пам’яттю для вас (рядок може зростати, і реалізація виділить вам більший буфер)
  • Інтерфейс програмування вищого рівня чудово працює з рештою STL.

std::stringмінуси: - два окремі екземпляри рядка STL не можуть спільно використовувати той самий базовий буфер. Отже, якщо ви передаєте значення, ви завжди отримуєте нову копію. - є певний штраф, але я б сказав, що якщо ваші вимоги не спеціальні, це незначно.


Насправді реалізації STL часто реалізують семантику copy-on-write для std :: string, тому передача їх за значенням взагалі не коштує дуже багато. Тим не менше, краще не покладатися на це, і взагалі краще все-таки передати посилання на const.

1
Деякі реалізації std :: string відмовились від реалізації COW. Більше того, він не такий тривіальний, як здається, забезпечує клас безпечного потоку (POSIX), сумісний зі стандартом. Див. Groups.google.fr/group/ifi.test.boa/browse_frm/thread/… або groups.google.fr/group/comp.programming.threads/browse_frm/…
Люк Ермітт

8

Вам слід розглянути можливість використання char*у таких випадках:

  • Цей масив буде передано в параметрі.
  • Ви заздалегідь знаєте максимальний розмір вашого масиву (ви знаєте його АБО нав'язуєте).
  • Ви не будете робити жодних перетворень на цьому масиві.

Насправді, в C ++ char*часто використовують для фіксованого малого слова, як параметри, ім'я файлу тощо ...


3
Масив не передано, вказівник на масив. Ось що таке вказівник - вказівник на об’єкт.
Коул Джонсон,

5

Коли використовувати c ++ std :: string:

  • рядки в цілому безпечніші за char *. Зазвичай, коли ви робите речі за допомогою char *, вам потрібно перевіряти речі, щоб переконатися, що все правильно, у класі рядків все це робиться за вас.
  • Зазвичай, використовуючи char *, вам доведеться звільнити виділену вами пам'ять, вам не потрібно робити це за допомогою рядка, оскільки він буде звільняти свій внутрішній буфер при руйнуванні.
  • Рядки добре працюють з ланцюжком потоків C ++, відформатований введення-вивід дуже простий.

Коли використовувати char *

  • Використання символу char * дає вам більше контролю над тим, що відбувається "за кадром", а це означає, що ви можете налаштувати ефективність, якщо вам потрібно.

3

Використовуйте (const) char * як параметри, якщо ви пишете бібліотеку. std :: реалізації рядків відрізняються між різними компіляторами.


Якщо ви пишете бібліотеку на C ++, макет std :: string - не єдине, про що вам слід турбуватися. Існує безліч потенційних несумісностей між двома реалізаціями. Використовуйте бібліотеки в C ++, лише якщо вони доступні у вихідному коді або скомпільовані саме для того компілятора, який ви використовуєте. Бібліотеки C, як правило, більш портативні, але в такому випадку у вас все одно немає std :: string.
Девід Торнлі,

Правда, std :: string - це не єдина проблема, але занадто багато зробити висновок "Використовувати бібліотеки в C ++ лише у тому випадку, якщо вони доступні у вихідному коді або скомпільовані саме для того компілятора, який ви використовуєте." Існують системи компонентів, які чудово працюють з різними компіляторами (наприклад, COM), і можна виставити інтерфейс C бібліотеці, яка внутрішньо записана на C ++ (наприклад, API Win32)
Nemanja Trifunovic

2

Якщо ви хочете використовувати бібліотеки C, вам доведеться мати справу з C-рядками. Те саме застосовується, якщо ви хочете виставити свій API на C.


2

Ви можете очікувати, що більшість операцій над std :: string (наприклад, наприклад find) будуть максимально оптимізовані, тому вони, швидше за все, виконуватимуть принаймні настільки ж, як і аналог чистого C.

Також варто зазначити, що ітератори std :: string досить часто перетворюються на вказівники на базовий масив char. Отже, будь-який алгоритм, який ви розробляєте поверх ітераторів, по суті ідентичний одному алгоритму поверх символу char * з точки зору продуктивності.

На що слід звернути увагу, наприклад operator[]: більшість реалізацій STL не виконують перевірку меж, і вони повинні перекласти це на ту саму операцію над базовим масивом символів. AFAIK STLPort за бажанням може виконувати перевірку меж, і в цей момент цей оператор буде трохи повільнішим.

То що вам приносить використання std :: string? Це позбавляє вас від ручного управління пам'яттю; зміна розміру масиву стає простішою, і вам, як правило, доводиться менше думати про звільнення пам’яті.

Якщо вас турбує продуктивність при зміні розміру рядка, є reserveфункція, яка може вам виявитися корисною.


1

якщо ви використовуєте масив символів у вигляді тексту тощо, використовуйте std :: string, більш гнучкий та простіший у використанні. Якщо ви використовуєте його для чогось іншого, наприклад, для зберігання даних? використовувати масиви (віддавати перевагу векторам)


1

Навіть коли продуктивність має вирішальне значення, краще використовувати vector<char>- це дозволяє розподілити пам'ять заздалегідь (метод резерву ()) і допоможе вам уникнути витоків пам'яті. Використання оператора vector :: [] веде до накладних витрат, але ви завжди можете витягти адресу буфера та проіндексувати його точно так, як якщо б це був символ *.


Але було б непогано використати якусь типову функціональність рядків і мати лише можливість вказати політику зберігання. Для цього дивіться посилання в моїй відповіді.
Анонім

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

Я неправильно зрозумів вашу відповідь, оскільки ви використовуєте вектор замість символу *, а не замість рядка ... У цьому випадку я згоден.
Джером

У використанні оператора [] не повинно бути накладних витрат. Дивись, наприклад, stackoverflow.com/questions/381621 / ...
Люк Hermitte

-1

AFAIK внутрішньо найбільш std :: string реалізує копію під час запису, семантика з урахуванням посилань, щоб уникнути накладних витрат, навіть якщо рядки не передаються посиланням.


5
Це більше не відповідає дійсності, оскільки копіювання на запис викликає серйозні проблеми масштабованості в багатопотоковому середовищі.
Сума,

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