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.

Historie 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.

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 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) nebo typ CString v MFC společnosti Microsoft. 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++ 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ů.

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“.

Implementační problémy Ř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. 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. 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.

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

Standardní řetězcové typy 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.

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. 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ě:

std::string original("aaaaaaa"); std::string string_copy = original; // vytvoří kopii char* ukazatel = &string_copy; // 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 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 (zvláště pro řetězce kratší než je velikost ukazatele). Optimalizace byla nakonec v C++11 zakázána, 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 konstantní referenci:

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

V C++17 byla přidána nová třída string_view, která obsahuje pouze ukazatel a délku dat a lze ji používat 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í #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 std::string je typedef pro určitou instanciaci šablonové třídy std::basic_string. 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 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. 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).

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.

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

Kritika 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.

Odkazy

Reference

Kategorie:C++ Kategorie:Standardní knihovna C++ C++