char * vs std :: string w c ++ [zamknięte]

81

Kiedy należy używać, std::stringa kiedy należy używać char*do zarządzania tablicami chars w C ++?

Wydaje się, że powinieneś użyć, char*jeśli wydajność (szybkość) jest kluczowa i jesteś skłonny zaakceptować ryzykowny biznes ze względu na zarządzanie pamięcią.

Czy są inne scenariusze do rozważenia?

jww
źródło

Odpowiedzi:

56

Możesz przekazywać ciągi std :: przez referencję, jeśli są duże, aby uniknąć kopiowania, lub wskaźnik do instancji, więc nie widzę żadnej rzeczywistej korzyści ze stosowania wskaźników znaków.

Używam std :: string / wstring dla mniej więcej wszystkiego, co jest rzeczywistym tekstem. char *jest jednak przydatny w przypadku innych typów danych i możesz być pewien, że zostanie zwolniony tak, jak powinien. W przeciwnym razie std :: vector jest drogą do zrobienia.

Prawdopodobnie są wyjątki od tego wszystkiego.

Skurmedel
źródło
8
czy jest różnica w wydajności przed tymi dwoma?
vtd-xml-author,
3
@ vtd-xml-author: Może trochę. Prosto char *prawie nie ma narzutów. Dokładnie std::stringnie wiem, co ma narzut , prawdopodobnie zależy to od implementacji. Prawie nie oczekuję, że narzut będzie znacznie większy niż w przypadku gołego wskaźnika char. Ponieważ nie posiadam kopii standardu, nie mogę tak naprawdę szczegółowo określić żadnych gwarancji udzielonych przez standard. Wszelkie różnice w wydajności prawdopodobnie będą się różnić w zależności od wykonywanych operacji. std::string::sizemoże przechowywać rozmiar obok danych znakowych i dzięki temu być szybszym niż strlen.
Skurmedel
2
Dlaczego nie użyć std :: string dla danych nietekstowych? Nie są zakończone zerowo, więc powinieneś być w stanie przechowywać tam wszystko, co chcesz.
Casey Rodarmor
1
@rodarmor Ty może przechowywać wszystko, co chcesz, ale jest to ryzykowne dotykowy jako ciąg przeznaczony jest do NUL łańcuchów znaków. Musisz uważać, aby używać tylko operacji bezpiecznych dla plików binarnych, np. append(const string&)I append(const char*, size_t)zamiast operator+=().
boycy
6
Jesteś pewny? Wiem, że wiele operacji zakłada, że ​​znak * jest łańcuchem zakończonym znakiem null, ale nie przychodzi mi do głowy żaden, który zakładałby, że std :: string nie zawiera żadnych wartości null.
Casey Rodarmor,
58

Mój punkt widzenia jest następujący:

  • Nigdy nie używaj znaku *, jeśli nie wywołujesz kodu „C”.
  • Zawsze używaj std :: string: jest łatwiejszy, bardziej przyjazny, zoptymalizowany, jest standardowy, zapobiega pojawianiu się błędów, został sprawdzony i działa.
Gal Goldman
źródło
13

Użycie nieprzetworzonego ciągu

Tak, czasami naprawdę możesz to zrobić. Używając const char *, tablic char alokowanych na stosie i literałów łańcuchowych możesz to zrobić w taki sposób, że nie ma w ogóle alokacji pamięci.

Pisanie takiego kodu często wymaga więcej przemyślenia i staranności niż użycie łańcucha lub wektora, ale przy użyciu odpowiednich technik można to zrobić. Dzięki odpowiednim technikom kod może być bezpieczny, ale zawsze musisz się upewnić, że kopiując do char [] albo masz jakieś gwarancje co do długości kopiowanego ciągu, albo z wdziękiem sprawdzasz i obsługujesz zbyt duże łańcuchy. Nie robienie tego sprawiło, że ta rodzina funkcji strcpy zyskała reputację niebezpiecznej.

Jak szablony mogą pomóc w pisaniu bezpiecznych buforów znaków

Jeśli chodzi o bezpieczeństwo buforów char [], pomocne mogą być szablony, ponieważ mogą one tworzyć enkapsulację do obsługi rozmiaru bufora za Ciebie. Szablony takie jak ten są wdrażane np. Przez firmę Microsoft w celu zapewnienia bezpiecznych zamienników strcpy. Poniższy przykład pochodzi z mojego własnego kodu, prawdziwy kod ma znacznie więcej metod, ale to powinno wystarczyć, aby przekazać podstawową ideę:

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;
}
Suma
źródło
1
+1 dla "Używając const char *, tablic char przydzielonych na stosie i literałów łańcuchowych, możesz to zrobić w taki sposób, że nie ma w ogóle alokacji pamięci." Ludzie zapominają, że „alokacja” stosu jest znacznie szybsza niż sterta.
NoSenseEtAl
char*stringi nie zawsze znajdują się na stosie. char *str = (char*)malloc(1024); str[1024] = 0;
Cole Johnson
@ColeJohnson Nie twierdzę, że mam na myśli tylko to, że jeśli chcesz, aby twój ciąg był przydzielony na stosie, musisz użyć const char * w połączeniu z literałami ciągów, a nie std :: string.
Suma
9

Jedną z sytuacji, których MUSISZ użyć, char*a nie, std::stringjest sytuacja, gdy potrzebujesz statycznych stałych łańcuchowych. Powodem tego jest to, że nie masz żadnej kontroli nad kolejnością modułów, które inicjalizują ich zmienne statyczne, a inny obiekt globalny z innego modułu może odwoływać się do twojego ciągu przed jego zainicjowaniem. http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Static_and_Global_Variables

std::string plusy:

  • zarządza pamięcią za Ciebie (ciąg może rosnąć, a implementacja przydzieli Ci większy bufor)
  • Interfejs programowania wyższego poziomu, dobrze współpracuje z resztą STL.

std::stringminusy: - dwie różne instancje ciągu STL nie mogą współużytkować tego samego podstawowego buforu. Więc jeśli przekażesz wartość, zawsze otrzymasz nową kopię. - istnieje pewna utrata wydajności, ale powiedziałbym, że jeśli nie masz specjalnych wymagań, jest to znikome.

thesamet
źródło
W rzeczywistości implementacje STL często implementują semantykę kopiowania przy zapisie dla std :: string, więc przekazywanie ich przez wartość wcale nie kosztuje dużo. Mimo to lepiej nie polegać na tym i generalnie lepiej jest i tak przekazać odniesienie do stałej.
1
Niektóre implementacje std :: string zrezygnowały z implementacji COW. Co więcej, nie jest to tak trywialne, jak się wydaje, że zapewnia (POSIX) bezpieczną dla wątków klasę zgodną ze standardem. Zobacz groups.google.fr/group/ifi.test.boa/browse_frm/thread/… lub groups.google.fr/group/comp.programming.threads/browse_frm/ ...
Luc Hermitte
8

Należy rozważyć użycie char*w następujących przypadkach:

  • Ta tablica zostanie przekazana w parametrze.
  • Znasz z góry maksymalny rozmiar swojej tablicy (znasz to LUB narzucasz).
  • Nie wykonasz żadnej transformacji na tej tablicy.

Właściwie w C ++ char*są często używane do ustalonych małych słów, jako opcji, nazwy pliku itp.

Jérôme
źródło
3
Tablica nie jest przekazywana, wskaźnik do tablicy. Tym właśnie jest wskaźnik - wskaźnikiem do obiektu.
Cole Johnson
5

Kiedy używać std :: string w języku C ++:

  • stringi są ogólnie bezpieczniejsze niż char *. Normalnie, kiedy robisz rzeczy z char *, musisz sprawdzać rzeczy, aby upewnić się, że wszystko jest w porządku, w klasie string wszystko to jest robione za Ciebie.
  • Zwykle podczas używania char * będziesz musiał zwolnić przydzieloną pamięć, nie musisz tego robić za pomocą łańcucha, ponieważ po zniszczeniu zwolni on swój wewnętrzny bufor.
  • Ciągi znaków działają dobrze z ciągami znaków C ++, sformatowane We / Wy jest bardzo łatwe.

Kiedy używać znaku *

  • Korzystanie z funkcji char * zapewnia większą kontrolę nad tym, co dzieje się „za” kulisami, co oznacza, że ​​w razie potrzeby można dostroić występ.
user88637
źródło
3

Użyj (const) char * jako parametrów, jeśli piszesz bibliotekę. Implementacje std :: string różnią się między różnymi kompilatorami.

Nemanja Trifunovic
źródło
Jeśli piszesz bibliotekę w C ++, układ std :: string nie jest jedyną rzeczą, o którą musisz się martwić. Istnieje wiele potencjalnych niezgodności między dwiema implementacjami. Używaj bibliotek w C ++ tylko wtedy, gdy są dostępne w źródle lub skompilowane dla konkretnego kompilatora, którego używasz. Biblioteki C są zwykle bardziej przenośne, ale w takim przypadku i tak nie masz std :: string.
David Thornley
To prawda, że ​​std :: string to nie jedyny problem, ale stwierdzenie „Używaj bibliotek w C ++ tylko wtedy, gdy są dostępne w źródle lub skompilowane dla konkretnego kompilatora, którego używasz”, to trochę za dużo. Istnieją systemy komponentów, które działają dobrze z różnymi kompilatorami (na przykład COM) i możliwe jest udostępnienie interfejsu C w bibliotece, która jest wewnętrznie napisana w C ++ (na przykład Win32 API)
Nemanja Trifunovic
2

Jeśli chcesz używać bibliotek C, będziesz musiał radzić sobie z napisami C. To samo dotyczy sytuacji, gdy chcesz udostępnić swój interfejs API C.

n0rd
źródło
2

Możesz spodziewać się większości operacji na std :: string (takich jak np find ) Będzie tak zoptymalizowana, jak to tylko możliwe, więc prawdopodobnie będą wykonywać co najmniej tak dobrze, jak czysty odpowiednik w C.

Warto również zauważyć, że iteratory std :: string dość często mapują wskaźniki do podstawowej tablicy char. Zatem każdy algorytm, który opracujesz na podstawie iteratorów, jest zasadniczo identyczny z tym samym algorytmem na szczycie char * pod względem wydajności.

Rzeczy, na które należy zwrócić uwagę, to np. operator[]- większość implementacji STL nie wykonuje sprawdzania granic i należy to przetłumaczyć na tę samą operację na bazowej tablicy znaków. AFAIK STLPort może opcjonalnie wykonywać sprawdzanie granic, w którym to momencie ten operator byłby nieco wolniejszy.

Więc co daje użycie std :: string? Zwalnia cię z ręcznego zarządzania pamięcią; zmiana rozmiaru tablicy staje się łatwiejsza i generalnie trzeba mniej myśleć o zwalnianiu pamięci.

Jeśli martwisz się o wydajność podczas zmiany rozmiaru ciągu, istnieje reservefunkcja, która może Ci się przydać.


źródło
1

jeśli używasz tablicy znaków w podobnym tekście itp. użyj std :: string, bardziej elastycznego i łatwiejszego w użyciu. Jeśli używasz go do czegoś innego, jak przechowywanie danych? używaj tablic (preferuj wektory)

RvdK
źródło
1

Nawet jeśli wydajność ma kluczowe znaczenie, lepiej wykorzystujesz vector<char>- umożliwia alokację pamięci z wyprzedzeniem (metoda Reserve ()) i pomaga uniknąć wycieków pamięci. Użycie vector :: operator [] prowadzi do narzutu, ale zawsze można wyodrębnić adres bufora i zindeksować go dokładnie tak, jakby był to znak *.

ostry
źródło
Ale byłoby miło użyć jakiejś typowej funkcjonalności ciągów i mieć tylko opcję określenia polityki przechowywania. Zobacz link w mojej odpowiedzi.
Anonimowy
To nie jest prawdziwa prawda. Jeśli weźmiesz pod uwagę, że wektor zostanie przydzielony w ciągłej przestrzeni pamięci, ponowna alokacja (w celu zwiększenia rozmiaru wektora) nie będzie w ogóle efektywna, ponieważ implikuje kopię poprzedniego fragmentu.
Jérôme
Nie zrozumiałem Twojej odpowiedzi, ponieważ używasz wektora zamiast znaku *, a nie zamiast ciągu… W tym przypadku zgadzam się.
Jérôme
Nie powinno być narzutów w użyciu operatora []. Zobacz na przykład stackoverflow.com/questions/381621/…
Luc Hermitte
-1

AFAIK wewnętrznie większość std :: string implementuje kopiowanie podczas zapisu, odwołuje się do semantyki liczonej, aby uniknąć narzutu, nawet jeśli ciągi nie są przekazywane przez referencję.

piotr
źródło
5
Nie jest to już prawdą, ponieważ kopiowanie podczas zapisu powoduje poważne problemy ze skalowalnością w środowisku wielowątkowym.
Suma
Jest to prawdą przynajmniej w przypadku implementacji STL w GCC.