Každý určitě má nějaký koníček. A v rámci svých koníčků člověk často dělá věci, které zdánlivě nemají smysl, nebo v dané době už jsou překonané. Prostě vynalézáme poněkolikáté kolo. Přesto to může být zábava
V tomto článku popíšu, jak jsem naprogramoval minimalistický http server v C++ 17. Začalo to přitom nevině, na začátku byla snaha zobrazit obsah adresáře v prohlížeči. Tím samozřejmě myslím stránky, které v tom adresáři byly uložené jako sada html souborů a přidržených stylů, obrázků a skriptů. Za normálních okolností si vystačíme s protokolem file:/// který drtivá většina prohlížečů zvládne, takže stačí otevřít zpravidla index.html přímo z adresáře a vše je vidět. Ne vždy ale stránky fungují, což má logiku, pokud je tam vyžadována nějaká komunikace se vzdáleným serverem, ale bohužel často se stává, že nefungují ani, když komunikaci nevyžadují. Na vině je delší dobu utlačovaný význam protokolu file:///, díky kterému prohlížeč mnoho věcí blokuje. Zejména různé moderní funkce jako média, poloha, XHR, atd, prostě přes file:/// nerozeběhnete. (ano XHR, třeba na stažení dalších souborů javascriptem)
Hledal jsem řešení, jak co nejjednodušeji přenést adresář do http prostředí. A místo abych jednoduše vzal nginx/apache a do jejich document_root strčil symlink na ten adresář, což by asi udělal každý, tak jsem otevřel stránky Google a našel… docela zajímavý github s implementací miniaturního http serveru v C. Vzpomněl jsem si na legendární slova Linuse Torvaldse o tom, že „C++ is a horrible language“ a rozhodl jsem se, že ukážu, že to tak není a že lze napsat cosi základního v C++ a to s elegancí. Výzva byla na světě.
Projekt minihttp server najdete na Githubu. Jedná se o minimalizovaný http server (pouze http a pouze verze 1.1 a to ještě limitované) napsaný v C++17. Snažil jsem se maximálně používat C++ a do čistého C přecházet jen v případě, kdy to bylo nutné – typicky kvůli Posixu, protože C++ ani ve verzi 17 neumí v základu například práci se sokety, a protože bylo podmínkou čistá implementace bez knihoven třetích stran, vyloučil jsem i použití boostu.
Samotný program se po přeložení ovládá jednoduše
minihttp localhost:8800 ./www
Program minihttp je naprogramován jako jeden jediný cpp soubor – obsahuje vše, co potřebuje a pro překlad si vystačí s gcc 8 a výše. Původní C implementace používá fork() pro vytváření workerů, moje implementace s výhodou používá standardní std::thread. Díky C++17 se konečně do normy dostává std::string_view a tento objekt se v programu hojně využívá zejména při parsování požadavků. Pokud program pracuje s descriptory a sockety, nepracuje s nimi nikdy přímo. Místo toho tam naleznete šablonu RAII, která obaluje tyto číselné deskriptory do třídy, která pak sleduje vlastnictví deskriptoru a je schopna jednotlivý deskriptor automaticky uzavřít jakmile ztratí platnost. Z výhodou se tady používá r-value reference a std::move()
Programátor začátečník by měl v kódu najít několik postupů jak docílit
U toho posledního se maličko zastavím. Viděl jsem mnoho implementací čtení klasického socketu a málokterá byla správně. Základní problém většiny implementací je ten, že funkce recv (nebo read) nemusí vždy přečíst tolik, kolik se od ní žádá. Pokud načte méně, pak je třeba čtení opakovat. Viděl jsem ale kód, kde se s tím nepočítalo a ten kód fungoval z 99%. V tom 1% se paket náhodou rozdělil přesně v tom místě, kde se očekával souvislý blok a výsledkem byl prostě crash.
Největší problém vždycky představuje čtení dat, které končí nějakým znakem. Většina čtecích funkcí totiž vyžaduje znát délku čtených dat a …pokud data končí nějakým znakem, jakou délku mám zadat, když to nevím? Pokud už načtu nějaký buffer a zjistím, že data, která parsuji, končí „kdesi uprostřed“, co udělat se zbytkem dat, aby se neztratila?
Trošku odlišný přístup je právě prezentován v třídě Conn. Tam najdete funkci, která se jmenuje jednoduše read(). Nemá žádné argumenty, pouze vrací std::string_view, který nese část načtených dat. Přijde mi totiž zbytečné, abych dopředu hlásil, kolik toho budu chtít. Přece dokud jsou data, tak čti. Volající pak dostává úseky dat tak jak přímo lezou ze streamu a které, mezitím než přilezou další, může nějak zpracovat. A když je nalezen konec, co udělat se zbytkem bufferu? K tomu tam existuje funkce put_back, která část bufferu vrátí a tak se žádná data neztratí, budou načtena další funkcí read().
Zmiňuji se o tom proto, že málokdy vidím API, které by takto bylo navrženo. Spíš častěji vidíme prototyp: size_t read(void *buffer, size_t velikost), což je velice nepohodlné.
Zjistil jsem, že po dopsání serveru jej i používám (!). Jasně, pořád jsem dost línej na to vytvořit si ten symlink. Ale protože mám víc vývojových prostředí a některé i ve virtualech, tak nemám na všech instalaci nginx/apache, a tak se ukázalo pohodlnější stáhnout a přeložit program z githubu, než se prokousávat nastavení webserveru. Chyběla mi tam možnost na určitou cestu v rámci stránek namapovat jinou URL, třeba proxy k serverovému API, zejména proto, abych se vyhnul nutností zapínat CORS na serveru (ne vždy je to možné).
Cílem tedy bylo mít možnost přeposlat určité requesty na jiný server. Zdánlivě jednoduchá úloha není jednoduchá jak se zdá. A nezápasím s tím jen já. Kolega z mého týmu je html/js programátor, který, protože trvale pracuje s javascriptem, je také vlastníkem plnohodnotné instalace node.js. Ten moje koníčky neocení, protože jeho nástroje tohle všechno umí v základu. Přesto nejednou zápasíme s konfigurací jeho developerského webového serveru tak, aby se domluvil s našimi servery poskytující API. Při každé příležitosti se vynořují jiné problémy. Tu nefunguje POST, tu si webserver při proxování nerozumí s certifikátem, onehdy to prostě nefungovalo vůbec a jak se nám to naposled podařilo rozjet se mne ani neptejte.
Když už jsem zmínil certifikáty, tak potřeba připojovat se k https mi nejprve vzala vítr z plachet. V rámci totální minimalizace bych nechtěl řešit openssl…
Nakonec jsem s k tomu vrátil s tím, že by http klient mohl být realizován externě, pomocí programu curl, ten přece zvládne vygenerovat libovolný request a na standardní výstup předat odpověď včetně hlaviček. Zbývalo jen vymyslet, jak ho napojit na minihttp. A tak vznikl protokol VSCGI, který je inspirován CGI protokolem. Vadilo mi, že CGI je velice „těžký“ a proto mým řešením je výrazně osekaný CGI protokol, jehož zkratka znamená: Very Stupid CGI protokol (nebo Very Simple?)
VSCGI je definován následovně: Zavádí se soubor s příponou .vscgi, který, pokud se nachází v cestě požadavku, a přitom může být uprostřed cesty, je spuštěn jako proces, přičemž obdrží parametry: Metodu (POST, GET, PUT), zbytek cesty (pokud se soubor nachází uprostřed) a verzi protokolu (HTTP/1.1). Zbytek hlaviček a tělo requestu je odeslán na standardní vstup. Spuštěný process se také může spolehnout, že request končí zavřením standardního vstupu, takže nemusí složitě odpočítávat bajty s Content-Lenght. Jakmile je request načten, očekává se, že process vygeneruje odpověď. Odpověď tentokrát musí být kompletní včetně hlaviček a tělíčka. Odpověď také končí zavřením standardního výstupu.
Zbytek proxování zařídí skript minihttp_proxy, který je v rámci zpracování požadavku zavolán. Mělo by to fungovat na první dobrou. Od toho okamžiku máte v rámci statických stránek v rámci domény localhost možnost posílat requesty na externí URL aniž by se jednalo o CORS requesty.
Jak jsem psal v úvodu, vzniklo to jako koníček, něco, co v zásadě nemá žádný velký význam. Přesto si myslím, že to někomu může připadnout užitečné, například začínajícím vývojářům. A protože necílím na zahraniční vývojáře, najdete v kódu všechny komentáře česky.
Jinak si myslím, že na adhoc web server to také může být užitečné, zvlášť pokud nemáte k dispozici webserver, pracujete na omezeném účtu (webserver potřebuje root práva k instalaci) a chcete si zobrazit obsah adresáře jako http stránky.
Stránky projektu: https://github.com/ondra-novak/minihttp
Souhlas, je evidentní, že autor v C++ moc kovaný není.
Namátkou
* funkce deklaruje statické i když jsou v anonymním namespace.
* v C++11 existuje constexpr (autor používá na stringové a číselné konstanty static const )
* obluda std::pair<std::string_view, std::string_view>[] místo std::map a ještě píše v komentáři, že si to má programátor při úpravě řadit podle abecedy :-D
* podivná šablona RAII (pro Socket a FileDesc je lepší vytvořit třídu, když už k nim má stejně nějaké funkce, zbytek stačí std::unique_ptr)
* použití operátoru () v SplitString
atd.
Mně tohle přijde legrační. Ondra pro mě vždycky byl autorita na poli hardcore C++. Jeho programátorský styl se mi nikdy nelíbil, protože sám mám rád mnohem jednodušší způsob programování (ano, v některých věcech jsem tak trochu céčkař, i když i já uznávám, že je dobré dát kód do tříd, pokud možno nealokovat pomět malloc/free atd..)
No a teď se dozvím, že tenhle hardcore C++ programátor není v C++ kovaný :-)
takhle sem to mastil nekdy na stredni skole. Komentare sem psal taky cesky, zejmena aby kod vypadal vedecky a aby ho kazdej tzv. pochopil. U "int a=1;" jsem taky psal "inicializace promenne". Mapy, stringy, listy sem si taky musel napsat lip nez jsou ty v stdlib/boostu. "setridenej list" sem taky nekdy asi pouzil a zoptimalizoval tak start programu o 1ms.
Je to vsechno v poradku... Kdyz je vam 15 :-D
Zdravím, děkuji za reakci, následně odpovím na následující námitky
> * funkce deklaruje statické i když jsou v anonymním namespace.
Primárně byly pouze statické, protože je velký rozdíl mezi funkcí v anonymním namespace a funkce statické
1. statické funkce se neexportují
2. funkce v anonymním namespace se exportují ale pod náhodným namespace
(aspoň takto to dělá msvc, nevím jaký je aktuální stav)
Obecně používám pomocné funkce deklarované jako statick. Jednou z výhod také je, že překladač většinou warningem označí funkce, které už nepoužívám.
anonymní namespace byl vložen proto, že některá verze GCC si stěžovala, že deklarace třídy je v cpp souboru (jako warning). Jinak bych to neřešil
> * v C++11 existuje constexpr (autor používá na stringové a číselné konstanty static const )
asi hodně podobné jako u toho static funkce. Před C++11 se to tak běžně psalo. Moc jsem to nesledoval a nevidím v tom velký rozdíl
> * obluda std::pair<std::string_view, std::string_view>[] místo std::map a ještě píše v komentáři, že si to má programátor při úpravě řadit podle abecedy :-D
Ta obluda je rychlejší než std::map, a zabírá méně místa. Běžně používám u staticky alokovaných asociativních polí (slovníků)
> * podivná šablona RAII (pro Socket a FileDesc je lepší vytvořit třídu, když už k nim má stejně nějaké funkce, zbytek stačí std::unique_ptr
Vaše řešení opět znamená, že místo toho, aby deskriptor dál byl uložen v intu, tak ho máte alokovaný v paměti a ještě potřebujete pointer. Nepřijde mi to, že bych si pomocí C++ pomohl. Proč mám pro něco co v C zabírá 8 bajtů alokovat dalších desítek bajtů jen proto, že si neumím napsat jazykovými prostředky třídu, která to zvládne bez toho? A proč je to špatně tak jak to mám já?
> * použití operátoru () v SplitString
Protože SplitString je funktor, který vrací další řetězec v pořadí. Je to efektivnější, než se zaobírat iterátory
Obecně iterační fuktor používám
auto x = a.create_iterator()
while (!!x) {
do_something(x())
}
nezvyklé? Ale dobře se s tím pracuje.
To so statickou funkciou v anonymnom namespace je naprosto spravne. Nechova sa tak len MSVC. Ale da sa to ovplyvnit aj inymi sposobmi, aj ked nie do takej miery a tak pohodlne ako klucovym slovom static.
Podla toho co som vyzistil dokonca bolo v plane v novsich standardoch KW static uplne vypustit, ale zislo z toho, pretoze anonymny namespace ho plnohodnotne nenahradza.
Jedina vec, co tam mne kole oci je using na aliasovanie typu. Co je zle na typedefe?
Ad typedef, tohle je velká otázka a řeší se na celém internetu
osobně jsem přesvědčen, že using je v normě obecnější, než typedef, které bych přenechal čistému C.
Rozhodně doporučuju přeložit komenty do angličtiny. Jak je to jednou venku, je jen otázka času, kdy to najdou lidé, kteří nerozumí česky a nějaké představy o "určeno pro" jsou prostě nesmyslné. Už jsem v životě viděl dost kódu, který byl určen jen pro čechy a stejně nakonec došlo k tomu, že tomu potřeboval porozumět ind, brazilec či francouz.
A místo abych jednoduše vzal nginx/apache a do jejich document_root strčil symlink na ten adresář, což by asi udělal každý... v podobné situaci jsem použil buď php -S localhost:8000, nebo python -m SimpleHTTPServer 8000, jenže málokdo je každý :-).
@Ondřej Novák
>> pole místo map
Takže typická předčasná optimalizace. Možná rychlejší řešení náchylná k chybám použítá místo standardních věcí. Přitom zbytek kódu není optimalizovaný vůbec (IMHO největší výkonostní propad bude spíš ve spouštění a zavírání vlákna pro každé spojení než v hledání v seřazeném poli vs. RB stromu). A i kdybys na tom trval, tak to řazení by stejně bylo lepší udělat jednou při inicialiaci a nespoléhat na programátora.
>> constexpr
constexpr má výhodu v tom, že překladač vůbec nemusí alokovat proměnnou a může jí všude inlinovat (compile time constant). U static const to obecně nepůjde.
>> šablona RAII
Problém RAII šablony je v tom, že nevhodně kombinuje procedurální C a objektový C++ přístup. Ten deskriptor socketu a souboru je typově nerozlišitelný i přestože označuje různý typ. Programátor to musí hlídat stejně jako v C. Kromě toho vytváří zbytečný stav, kdy může být nenainicializovaný. Třída socket a filedescriptor s metodami read/write, s open v constructoru a close v destructoru bude znamenat zhruba stejně kódu, ale bude jasně vidět co se tam děje a zmizí mezistav neotevřeného souboru/socketu. Navíc oproti standardním pointerům nebo scope guardům je ta šablona hodně nedotažená. Jinak to vysvětlení je zase ukázkový příklad předčasné optimalizace.
>> static
Není pravda co píšeš, anonymní namespace má od C++11 "internal linkage". Navíc podle standardu bylo tohle použití static chvíli označeno jako deprecated, ale nakonec zůstává kvůli kompatibiltě s C (např. protože nemůžeš vytvářet lokální typy). MSVC není zrovna dobrá reference standardu a ten kód je stejně psaný pro unix.
>> operátor()
Pokud by se ta funkce jmenovala podle toho co dělá, tak si toho asi nevšimnu. Takhle to zbytečně znepřehledňuje kód a není k tomu žádný důvod.
> Takže typická předčasná optimalizace. Možná rychlejší řešení náchylná k chybám použítá místo standardních věcí.
KDE je to náchylné k chybám? No a CO je na tom nestandardního? Hlavním důvodem této volby je ZEJMÉNA ušetřit si psaní No jen si to vemte. Kdybych zvolil mapu, znamená to beztak deklarovat seznam dvojic, což už tam mám. Dále bych musel deklarovat promenou typu std::map a v nějaké inicializaci do ní nakopírovat ten seznam. A nakonec vlastní vyhledávání bude naprosto stejné psaní, jen se podívej na to místo, kde se to používá. Místo std::lower_bound, tam bude mapa.find(). Ale zbytek bude naprosto stejný. Takže namísto abych se s tím takhle trápil jsem prostě vzal to pole, už seřazený a použil std::lower_bound, což je imho standardní stejně jako mapa. Navíc s bonusem toho, že to zabírá méně místa, a je rychlejší, protože nemusí dereferencovat pointery, ale používá půlení se stejnou složitostí.
Seřadit 10 položek lze velice rychle pohledem. Seřadit 100 položek do zdrojáku lze snadno příkazem sort z terminálu. Pokud je to něco neměnné, statického, tak tohle je pro mě přímější cesta.
>> constexpr
Teď jste to vy, kdo předčasně optimalizuje. Pokud je mi známo, tak běžně překladač pozná, zda se u static const má zakládat proměnná nebo ne. Pokud vím, tak constexpr jen vyžaduje výpočet během kompilace. To rozšíření constexpr na static const a jestě k tomu inline je sice až v C++17 (což bych tedy pravda asi měl využít), ale obecně mám nedůvěru k věcem, kterým se často mění význam a tohle je jedna z těch věcí.
>> šablona RAII
Opět je to spíš důsledek lenosti psaní. Abych ty deskriptory měl plnohodnotné, musel bych všechny třídy deklarovat i se všema metodama a wrapovat je na posixové funkce. A to na to jsem línej. Takže se ty deskriptory používají prostě namísto číselných intů v posixových funkcí přímo. Nač bych se měl zabývat tím, že udělám plnohodnotnou třídu Socket se všemi funkcemi? Jen abych dosáhl nějaké 100% úrovně čistoty? Nehledě na to, že to moc dobře nejde, protože v unixovém světě je všechno deskriptor, takže bych stejně neuměl rozlišit soubor od soketu. Spoustu věcí je společných, spoustu věcí ne. Ale pokud máte víc času , tak si nad tím postavte třídní hierarchii.
Jinak krásně ukazujete, že jste kód zas dobře nestudoval. Onen zbytečný stav "neinicializovaný" není zbytečný, protože jinak by vám nefungoval move constructor, který musí do toho objektu "odkud" se přesouvá něco dát, aby to se to pak destruktor nezkoušel zdestruovat. Navíc neplatný stav je též součástí posixových deskriptorů, takže, proč ho tam nepřiznat. Když zavoláte funkci socket() a ona vám vrátí -1, tak je to neplatný deskriptor a já snadno poznám, že je něco blbě.
Jistě bych mohl zavolat
auto s = std::make_unique<Socket>(AF_INET,....)
a v konstruktoru vyhazovat výjimku když to selže.
a vůbec si s tím pohrát.
dokonce bych mohl udelat auto s = std::make_shared<Socket>(...) a měl bych ho sdílenej!
ale výsledkem by byl delší kód, nevím jestli by byl o dost přehledný a byl by samozřejmě pomalejší a nenažranější. Ale možná bych se cítíl víc jako C++ guru ... možná.
C++ jazyk je krásný právě tím, že nepředepisuje, jak se věci mají dělat. A prostě toto řešení mi přišlo rychleji naprogramované. A to zejména
>> static
Někdo vám nahoře napsal, že to právě ještě není vyřešeno, takže až to norma vyřeší, tak to začnu používat po novu. Zatím pořád anonymní namespace má external linkage, bohužel.
>> operátor()
Třída se jmenuje SplitString - je to málo popisné? Operátor () by se mohl jmenovat get_next().
Kdo zná trošku můj kód, tak právě funktory často používám a též doporučuju všem. Protože mají velice univerzální použití. Pokud mám funkci, která čte nějaký data v nějakém formátu, nejlepší je, když ten vstup dostane jako T (šablona) a ona sama pak to T chápe jako funkci, kterou když zavolá, obdrží další položku ze vstupu. Takto to najdete v jakémkoliv kódu, který kdekoliv mám. SplitString je přesně dtto. Funguje jako funkce, která vrací další hodnotu ze vstupu.
Je to trošku procedurální programování, trošku si hraní s lambdami, ale to mi přesně vyhovuje. Lambdy a funkce, které mají stav, miluju tuhle část normy.
Ale jistě mohl bych napsat funkci split_string, která vrací vektor rozsekaného řetězce - a už jsme opět u toho, zase z toho je moloch, který alokuje někde nějaký buffer takže je to pomalé a komplikované
Ad předčasná optimalizace? Ale nesmysl... to jsou osvědčené postupy, nad kterými prostě nepřemýšlím, nesedím u toho a neříkám, "tak jak to zoptimalizujeme". Je to ve stylu "tohle vždycky fungovalo nejlépe, tak to použijeme". Třeba použití std::string_view - jistě by každý všude měl std::string, ale - předčasná optimalizace, ušetříme alokaci tím, že budeme vytvářet pohledy na stringy - a já jsem rád, že se to do normy dostalo, protože to nepovažuju za předčasnou optimalizaci, ale za normální způsob programování. Tohle tam mělo být dávno.
"Hlavním důvodem této volby je ZEJMÉNA ušetřit si psaní"
:-D
static std::map<std::string,std::string> mime_types { {"css","text/css;charset=utf-8"}, {"gif","image/gif"}, {"htm",TEXT_HTML_CHARSET_UTF_8}, {"html",TEXT_HTML_CHARSET_UTF_8}, {"jpg","image/jpeg"}, {"js","text/javascript"}, {"json","application/json"}, {"png","image/png"}, {"svg","image/svg"}, {"xml","text/xml"} };
@KubaV
Okaj umíte to napsat stejným počtem písmenek. Přesto je to pomalejší a paměťově více náročný. Všechno prostě při psaní beru v úvahu.
PROČ MÁM SCHVÁLNĚ VYTVÁŘET POMALEJŠÍ KÓD?
Proč inicializovat mime_types v runtime, když to jde celé udělat v compile time? Mám pocit, že jsi ještě úplně neobjevil možnosti constexpr. Tady se můžeš inspirovat: https://github.com/serge-sans-paille/frozen
Ano, existuje stovka dalších způsobů, jak to naimplementovat, aby to nemusel řadit člověk, ale stroj. Jde to dokonce udělat v compile time, není nutné nic řadit a inicializovat v runtime. Takže se nediv, že se lidem nelíbí řešení, kdy to musí řadit programátor a ještě se to navíc zbytečně inicializuje v runtime.
@lopata - zas bych porušil požadavek zadání "jedno cpp file bez knihoven třetich stran". Jako že frozen je prostě knihovna třetí strany. Protože pokud bych tam tenhle požadavek neměl, pak by řešením bylo boost asio, " na co si tu vlastně hraješ?"
Nevím jak jste přišel na to, že se to inicializuje v runtime. Debugoval jste to? Tam se pouze deklaruje staticky pole of string_view. Jestli se string_view nějak runtime inicializuje nevím. Ano, možná by vyřešilo tam před to dát constexpr, ale to považuju za detail, o který nemá smysl se hádat. Zbytek je compile time a to že si to předsortím dopředu jen zjednodušuje jinak nutnost psát si sort v constexpr, což by zase šlo proti miniaturizaci. A sorry, je fakt pro mne jednodušší si to předsortit, než vymýšlet konstrukci, jak to nechat udělat stroj.
Ondro ? Nepsal si to jeste v szn nahodou ? Nebo je to dalsi verze ? Btw. neco hodne podobnyho kdysi psal taky Eda :D
@chleba
Už od doby Seznamu jsem měl rozepsanou skoro desítku http serverů, pokusných i pak produkčních, první dokonce v Lištice pro IE, protože IE nemělo žádnou možnost, jak z doplňku naservírovat html obsah na stránku. Rozhodně ale nebyl takto miniaturní.
Dále vznikl http server v rámci projektu JsonRPCServer, který obsahoval http server. Aktuálně používam simpleServer (můj github).
Tohle vzniklo .... hm jen jako koníček a celkem nedávno.
"Někdo vám nahoře napsal, že to právě ještě není vyřešeno, takže až to norma vyřeší, tak to začnu používat po novu. Zatím pořád anonymní namespace má external linkage, bohužel."
No nevim:
§ 6.5.4
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has
internal linkage. All other namespaces have external linkage.
static totiž neumožňuje nastavit internal linkage pro struktury šablony apod. Proto se ho snažili zrušit a vrátil se jen kvůli zpětné kompatibilitě s C.
@kutr
No ale stejně to je nějaký rozbitý, protože kdyby to mělo internal linkage, tak to nejde přeložit, protože tam používám v šablonách pointery jako argument, který vyžadují mít external linkage, přestože jsou v anonymním namespace, tak si překladač nestěžuje.
Nicméně nevidím nic špatného na tom používat static u funkcí. Stejně tak se můžeme hádat, jestli je lepší použít typedef nebo using, constexpr nebo static const, atd... a to nikam nevede. Kdyžtak na google najdete hodně takových diskuzí, jestli máte potřebu se hádat, tak směle do toho.
Asi by bylo lepší postupně odhodit deprecated nástroje ze starších verzí C++, ale jak říkám, dokud v tom není jasno, já jsem opatrnej.
Nepotřebuju nic debugovat, abych věděl, že se to v runtime inicializovat bude z mnoha důvodů:
1. Není to const, compiler musí vytvořit storage a celou strukturu inicializovat, protože se to může kdykoliv změnit.
2. I kdyby to const bylo, stejně se to bude inicializovat, protože konstruktor string_view je constexpr basic_string_view(const CharT* s), musí to spočítat délku s. Mohlo by to být bez inicializace v runtime, kdyby to celé bylo constexpr, ale není.
3. Konstruktor string_view není noexcept, takže teoreticky může vyhodit výjimku, pokud by se to stalo, musí to vyhodit výjimku i v runtime, nejde to optimalizovat. Kdyby to celé bylo constexpr, tak to může vyhodit výjimku v compile time, což by spadlo už během kompilace, takže s constexpr by to mohlo být bez inicializace v runtime.
Jak už jsem psal, měl bys dostudovat constexpr, nemáš úplně jasno v tom, k čemu to je a co to dělá. Od C++20 budou constexpr i knihovní funkce včetně std::sort, takže i taková věc jako std::array<std::pair<std::string_view, std::string_view>> půjde elegantně setřídit v compile time.
Intenzivně se zabývám programováním zejména v jazyce C++. Vyvíjím vlastní knihovny, vzory, techniky, používám šablony, to vše proto, aby se mi usnadnil život při návrhu aplikací. Pracoval jsem jako programátor ve společnosti Seznam.cz. Nyní jsem se usadil v jednom startupu, kde vyvíjím serverové komponenty a informační systémy v C++
Přečteno 51 057×
Přečteno 23 935×
Přečteno 22 867×
Přečteno 20 949×
Přečteno 17 755×