Práce s řetězci v C++
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
[editovat | editovat zdroj]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.
Kritika
[editovat | editovat zdroj]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]
Odkazy
[editovat | editovat zdroj]Reference
[editovat | editovat zdroj]V tomto článku byl použit překlad textu z článku C++ string handling na anglické Wikipedii.
- ↑ a b SEACORD, Robert C., 2013. Secure Coding in C and C++. [s.l.]: Addison-Wesley. Dostupné online. ISBN 9780132981972.
- ↑ OUALLINE, Steve, 2003. Practical C++ Programming. [s.l.]: O'Reilly.
- ↑ 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.
- ↑ a b SOLTER, Nicholas A.; KLEPER, Scott J., 2005. Professional C++. [s.l.]: John Wiley & Sons. Dostupné online. ISBN 9780764589492. S. 23.
- ↑ a b BLANCHETTE, Jasmin; SUMMERFIELD, Mark, 2008. C++ GUI Programming with Qt4. [s.l.]: Pearson Education. Dostupné online. ISBN 9780132703000.
- ↑ wstring_convert Class [online]. docs.microsoft.com, 2021-08-03 [cit. 2021-12-26]. Dostupné online.
- ↑ a b MEYERS, Scott, 2012. Effective STL. [s.l.]: Addison-Wesley. Dostupné online. ISBN 9780132979184. S. 64–65.
- ↑ 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.
- ↑ 21334 – Lack of Posix compliant thread safety in STD::basic_string [online]. Dostupné online.
- ↑ SUTTER, Herb, 1999. Optimizations That Aren't (In a Multithreaded World). C/C++ Users Journal. Roč. 17, čís. 6. Dostupné online.
- ↑ std::basic_string_view – cppreference.com [online]. en.cppreference.com [cit. 2016-06-23]. Dostupné online.
- ↑ C++ reference for basic_string [online]. Cppreference.com [cit. 2011-01-11]. Dostupné online.
- ↑ 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.
- ↑ C++11 Paper N3336 [online]. Programming Language C++, Library Working Group, 2012-01-13 [cit. 2013-11-02]. (Open Standards). Dostupné online.
- ↑ 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]
- ↑ char_traits – C++ Reference [online]. [cit. 2015-08-01]. Dostupné online.
- ↑ SUTTER, Herb. Monoliths "Unstrung" [online]. gotw.ca [cit. 2015-11-23]. Dostupné online.