Přeskočit na obsah

Práce s řetězci v C++

Z Wikipedie, otevřené encyklopedie

Programovací jazyk C++ má podporu pro práci s řetězci, která je z větší části implementována ve standardní knihovně C++. Standard jazyka definuje několik řetězcových typů, některé zděděné z jazyka C, jiné navržené tak, aby využívaly vlastnosti jazyka C++, jako jsou třídy a RAII. Nejpoužívanější z nich je std::string.

Protože nejstarší verze jazyka C++ měly pouze „nízkoúrovňovou“ funkčnost a konvence pro práci s řetězci z jazyka C, postupem času bylo vytvořeno několik vzájemně nekompatibilních návrhů tříd pro práci s řetězci, které se stále používají místo std::string, takže programátoři v C++ mohou potřebovat v jedné aplikaci pracovat s více konvencemi.

Od roku 1998 je hlavním datovým typem pro řetězce ve standardním jazyce C++ typ std::string, který však nebyl součástí C++ vždy. Z jazyka C zdědilo C++ konvenci používání řetězců zakončených znakem s kódem nula, které jsou reprezentovány ukazatelem na jejich první prvek, a knihovnu funkcí, které s takovými řetězci manipulují. Řetězcové literály, jako "hello", i v moderním standardním jazyce C++ stále označují pole znaků zakončené znakem NUL.[1]

Použití tříd jazyka C++ k implementaci řetězcového typu přináší při použití automatizované správy paměti několik výhod, snížení nebezpečí přístupu mimo meze[2] a intuitivnější syntaxi pro porovnávání a zřetězování řetězců. Proto bylo vytvoření takové třídy velmi lákavé. Postupem času vytvořili vývojáři aplikací, knihoven a frameworků v jazyce C++ vlastní, nekompatibilní reprezentace řetězců, jako je například reprezentace v knihovně Standard Components společnosti AT&T (první taková implementace z roku 1983)[3] nebo typ CString v MFC společnosti Microsoft.[4] V důsledku toho i po standardizaci řetězců typem std::string starší aplikace stále často obsahují takové vlastní typy řetězců a knihovny mohou očekávat řetězce ve stylu jazyka C, takže v programech v jazyce C++[1] je „prakticky nemožné“ vyhnout se používání několika řetězcových typů, a programátoři se před zahájením každého projektu musí rozhodnout pro požadovanou reprezentaci řetězců.[4]

Při zpětném pohledu označil autor jazyka C++ Bjarne Stroustrup v roce 1991 absenci standardního řetězcového typu (a některých dalších standardních typů) v C++ 1.0 za nejhorší chybu, které se při jeho vývoji dopustil; „jejich absence vedla k tomu, že všichni znovu vynalézali kolo a zbytečně se různily nejzákladnější třídy“.[3]

Implementační problémy

[editovat | editovat zdroj]

Řetězcové typy různých výrobců mají různé implementační strategie a výkonnostní charakteristiky. Některé typy řetězců používají zejména strategii copy-on-write, kdy operace jako např.

string = "hello!";
string b = a; // Kopírovací konstruktor

ve skutečností hned nezkopíruje obsah a do b, ale oba řetězce sdílí obsah a počet odkazů na obsah se zvýší o jedničku. Skutečné kopírování je odloženo, dokud se nemá provést operace, která může změnit obsah jednoho z řetězců, např. připojení znaku na konec jednoho z řetězců. Kopírování při zápisu může v kódu používajícím řetězce způsobit zásadní změny výkonu (některé operace jsou mnohem rychlejší a některé mnohem pomalejší). Typ std::string již copy-on-write nepoužívá, ale mnoho (možná většina) alternativních řetězcových knihoven ji stále používá.

Některé implementace řetězců pracují místo s bajtovými řetězci s 16bitovými nebo 32bitovými kódovými body, což mělo usnadnit zpracování textů v Unicode.[5] To však znamená, že konverze na tyto typy z std::string nebo z pole bajtů je závislá na „locales“ a může vyhazovat výjimky.[6] Veškeré výhody zpracování 16bitových kódových jednotek zmizely se zavedením kódování UTF-16 s proměnlivou šířkou (i když stále existují výhody, pokud je třeba komunikovat se 16bitovým API, např. Windows). Příkladem je QString v knihovně Qt.[5]

Nevýhodou implementací řetězců třetích stran byly také rozdíly v syntaxi pro získání nebo porovná podřetězců nebo pro vyhledávání v textu.

Standardní řetězcové typy

[editovat | editovat zdroj]

Standardní reprezentací textových řetězců od verze C++98 je třída std::string, která poskytuje některé typické řetězcové operace jako porovnávání, zřetězení, hledání a nahrazování, a funkce pro získání podřetězců. Je možné vzájemně převádět řetězce mezi typem std::string a řetězci ve stylu jazyka C.[7]

Jednotlivé jednotky tvořící řetězec jsou typu char, o velikosti nejméně (skoro vždy) 8 bitů. V moderní použití se často nejedná o „znaky“, ale o části vícebajtového kódování znaků, např. UTF-8.

Strategie copy-on-write byla v prvním standardu C++ pro std::string záměrně povolena, protože byla považována za užitečnou optimalizaci, a používaly ji téměř všechny implementace.[7] Objevovaly se však chyby, především operator[] vracel nekonstantní referenci, aby bylo možné snadno napodobovat manipulace s řetězci v jazyce C na místě (takový kód často předpokládá, že každý znak zabírá jeden bajt, což po nástupu UTF-8 není udržitelné!) To umožnilo následující kód, který ukazuje, že musí vytvořit kopii, přestože se téměř vždy používá pouze k prohledání řetězce a ne k jeho změně:[8][9]

  std::string original("aaaaaaa");
  std::string string_copy = original; // vytvoří kopii
  char* ukazatel = &string_copy[3]; // někteří zkoušeli, aby operator[] vracel „trikovou“ třídu, ale to jej příliš komplikuje
  arbitrary_code_here(); // žádné optimalizace toto neopraví
  *ukazatel = 'b'; // pokud operator[] nevytvořil kopii, toto způsobí nežádoucí změnu obsahu original

Toto způsobilo, že některé implementaceŠablona:Které přestaly používat copy-on-write. Také se ukázalo, že na moderní procesorech má zamykání potřebné pro kontrolu nebo změnu počtu odkazů má v multithreadových aplikacích větší režii než zkopírování krátkého řetězce[10] (zvláště pro řetězce kratší než je velikost ukazatele). Optimalizace byla nakonec v C++11 zakázána,[8] s výsledkem, že dokonce při předávání std::string jako argumentu funkce, např.

void print(std::string s) { std::cout << s; }

je třeba očekávat, že se bude provádět úplná kopie řetězce do nově přidělené paměti. Běžným idiomem, který má takovému kopírování zabránit, je použít jako konstantní referenci:

void print(const std::string& s) { std::cout << s; }

V C++17 byla přidána nová třída string_view[11] která obsahuje pouze ukazatel a délku dat pouze pro čtení, při jejímž použití je předávání argumentů daleko rychlejší než ve výše uvedených příkladech:

void print(std::string_view s) { std::cout << s; }
...
  std::string x = ...;
  print(x); // nekopíruje x.data()
  print("toto je řetězcový literál"); // také nekopíruje znaky!
...

Příklad použití

[editovat | editovat zdroj]
#include <iostream>
#include <iomanip>
#include <string>

int main() {
    std::string foo = "fighters";
    std::string bar = "stool";
    if (foo != bar) std::cout << "Řetězce se liší!\n";
    std::cout << "foo = " << std::quoted(foo)
              << " zatímco bar = " << std::quoted(bar);
}

Příbuzné třídy

[editovat | editovat zdroj]

std::string je typedef pro určitou instanciaci šablonové třídy std::basic_string.[12] Jeho definice je v hlavičkovém souboru <string>:

using string = std::basic_string<char>;

Tedy string poskytuje funkčnost třídy basic_string pro řetězce, jejichž prvky jsou typu char. Existuje podobná třída std::wstring, jejíž řetězce jsou tvořeny prvky typu wchar t, a která se nejčastěji používá pro ukládání textu v kódování UTF-16 na Microsoft Windows a pro kódování UTF-32 na většině unixových platforem. Standard C++ však nevynucuje interpretaci wchar t jako kódových bodů Unicode nebo kódových jednotek na tyto typy a nezaručuje, že wchar_t obsahuje více bitů než char[13] Pro vyřešení některých nekompatibilit způsobených vlastnostmi wchar_t byly od C++11 doplněny dvě nové třídy: std::u16string a std::u32string (pro řetězce tvořené novými typy char16_t a char32_t), které mají daný počet bitů na kódovou jednotku na všech platformách.[14] V C++11 byly také zavedeny nové řetězcové literály pro 16bitové a 32bitové „znaky“ a syntaxe pro zadávání kódových bodů Unicode do řetězců zakončených znakem s kódem nula (ve stylu jazyka C).[15]

Je zaručeno, že basic_string bude specializovatelný na libovolný typ, který je doprovázen strukturou char_traits. Ve verzi C++11 je požadováno,, aby byly implementované pouze specializace pro char wchar_t char16_t a char32_t.[16]

Typ basic_string je také kontejner ze standardní knihovny, a proto lze na kódové jednotky v řetězci použít algoritmy standardní knihovny.

Herb Sutter považuje návrh typu std::string za příklad nevhodného monolitického návrhu a tvrdí, že ze 103 členských funkcí této třídy v C++98, jich 71 mohlo být odděleno bez ztráty efektivity implementace.[17]

V tomto článku byl použit překlad textu z článku C++ string handling na anglické Wikipedii.

  1. a b SEACORD, Robert C., 2013. Secure Coding in C and C++. [s.l.]: Addison-Wesley. Dostupné online. ISBN 9780132981972. 
  2. OUALLINE, Steve, 2003. Practical C++ Programming. [s.l.]: O'Reilly. 
  3. a b STROUSTRUP, Bjarne, 1993. A History of C++: 1979–1991. In: Proc. ACM History of Programming Languages Conf.. [s.l.]: [s.n.]. Dostupné online.
  4. a b SOLTER, Nicholas A.; KLEPER, Scott J., 2005. Professional C++. [s.l.]: John Wiley & Sons. Dostupné online. ISBN 9780764589492. S. 23. 
  5. a b BLANCHETTE, Jasmin; SUMMERFIELD, Mark, 2008. C++ GUI Programming with Qt4. [s.l.]: Pearson Education. Dostupné online. ISBN 9780132703000. 
  6. wstring_convert Class [online]. docs.microsoft.com, 2021-08-03 [cit. 2021-12-26]. Dostupné online. 
  7. a b MEYERS, Scott, 2012. Effective STL. [s.l.]: Addison-Wesley. Dostupné online. ISBN 9780132979184. S. 64–65. 
  8. a b MEREDITH, Alisdair; BOEHM, Hans; CROWL, Lawrence; DIMOV, Peter, 2008. Concurrency Modifications to Basic String [online]. ISO/IEC JTC 1/SC 22/WG 21, 2008 [cit. 2015-11-19]. Dostupné online. 
  9. 21334 – Lack of Posix compliant thread safety in STD::basic_string [online]. Dostupné online. 
  10. SUTTER, Herb, 1999. Optimizations That Aren't (In a Multithreaded World). C/C++ Users Journal. Roč. 17, čís. 6. Dostupné online. 
  11. std::basic_string_view – cppreference.com [online]. en.cppreference.com [cit. 2016-06-23]. Dostupné online. 
  12. C++ reference for basic_string [online]. Cppreference.com [cit. 2011-01-11]. Dostupné online. 
  13. GILLAM, Richard, 2003. Unicode Demystified: A Practical Programmer's Guide to the Encoding Standard. [s.l.]: Addison-Wesley Professional. Dostupné online. ISBN 9780201700527. S. 714. 
  14. C++11 Paper N3336 [online]. Programming Language C++, Library Working Group, 2012-01-13 [cit. 2013-11-02]. (Open Standards). Dostupné online. 
  15. STROUSTRUP, Bjarne, 2013. The C++ Programming Language. [s.l.]: Addison Wesley. Dostupné v archivu pořízeném dne 2015-11-25. S. 179. [nedostupný zdroj]
  16. char_traits – C++ Reference [online]. [cit. 2015-08-01]. Dostupné online. 
  17. SUTTER, Herb. Monoliths "Unstrung" [online]. gotw.ca [cit. 2015-11-23]. Dostupné online.