Příprava pro Junior C++ vývojáře

Otázky na pohovor pro Junior C++ backend vývojáře

15 vybraných otázek na pohovor v C++ pro juniorní backend vývojáře, kteří potřebují jasně vysvětlit základy jazyka a principy vlastnictví.

Spustit Junior C++ AI pohovorKreditní karta není potřeba. K dispozici je 1 bezplatná lekce.
Procvičování technických pohovorů v angličtiněRežim pro lidi, pro které angličtina není rodným jazykem, určený k procvičování technických pohovorů.

Resource Management

1Vysvětlete koncept RAII (Resource Acquisition Is Initialization) a jak utváří správu prostředků bezpečnou proti výjimkám v backendových službách v C++.

RAII (Resource Acquisition Is Initialization) znamená, že objekt v C++ vlastní prostředek a uvolňuje jej ve svém destruktoru. Protože se lokální objekty ničí automaticky, když skončí jejich životnost (lifetime) nebo rozsah platnosti (scope), a to i během rozvíjení zásobníku (stack unwinding) při výjimce, poskytuje RAII deterministický úklid a zajišťuje, že zpracování chybových stavů je bezpečné proti výjimkám (exception-safe). V backendových službách se to netýká jen paměti, ale také deskriptorů souborů, soketů, zámků mutexů, databázových spojení (database handles), transakcí a dalších systémových nebo aplikačních prostředků.

Zkusit odpovědět na tuto otázku s trenérem AI

Správa paměti

2Porovnejte `std::unique_ptr` a `std::shared_ptr` a popište, kdy je který z nich vhodné použít v backendových API.

`std::unique_ptr` představuje výhradní vlastnictví: je levný (bez režie), přesunutelný (movable), ale nikoli kopírovatelný, a je vhodný pro jediného vlastníka nebo pro API (Application Programming Interface), která předávají vlastnictví. `std::shared_ptr` představuje sdílené vlastnictví: je kopírovatelný a udržuje objekt naživu pomocí počítání referencí, dokud jej neuvolní poslední silný vlastník. V backendových API používejte `unique_ptr`, když se předává vlastnictví, `shared_ptr` pouze tehdy, když více nezávislých vlastníků musí prodloužit životnost, a reference nebo surové ukazatele (raw pointers) pro přístup bez vlastnictví. Pro vytváření objektů `shared_ptr` se obvykle upřednostňuje `std::make_shared`, protože je efektivní a bezpečný z hlediska výjimek.

Zkusit odpovědět na tuto otázku s trenérem AI

Typový systém

3Co je sémantika přesunu (move semantics) a jak implementujete správný konstruktor přesunu a operátor přiřazení přesunem?

Sémantika přesunu umožňuje v C++ převádět prostředky z dočasných nebo jinak postradatelných objektů namísto jejich kopírování. Používá reference na r-hodnoty, jako je `T&&`, a funkci `std::move`, což je ve skutečnosti přetypování, které umožňuje výběr přetížených funkcí pro přesun; samotné `std::move` nic nepřesouvá. Správný konstruktor přesunu (move constructor) inicializuje nový objekt tak, že převezme prostředek zdrojového objektu a zdroj ponechá ve validním, zničitelném a přiřaditelném stavu. Správný operátor přiřazení přesunem (move assignment operator) přesouvá data do existujícího objektu, ošetřuje nebo toleruje přiřazení sobě samému (self-assignment), uvolňuje nebo znovu využívá aktuální prostředek cíle, přebírá zdrojový prostředek a ponechává zdrojový objekt v bezpečném stavu. Operace přesunu by měly být často označeny jako `noexcept`, aby je standardní kontejnery mohly používat při realokaci a zároveň zachovaly záruky ohledně výjimek.

Zkusit odpovědět na tuto otázku s trenérem AI

4Popište const-korektnost (const-correctness) v C++ API (Application Programming Interface) a jak efektivně navrhovat konstantní členské funkce (`const`).

Const-korektnost znamená vyjádření prostřednictvím typového systému, které operace nemění pozorovatelný nebo logický stav objektu. Členská funkce typu `const` má objekt `this` s kvalifikátorem `const`, takže nemůže měnit datové členy, které nejsou označené jako `mutable`, ani volat členské funkce bez kvalifikátoru `const` na stejném objektu. Správný návrh API označuje dotazy pouze pro čtení jako `const`, vrací hodnoty nebo `const` reference/ukazatele, pokud je to vhodné, a vyhýbá se odhalování měnitelného vnitřního stavu z `const` funkcí. Klíčové slovo `mutable` by mělo být vyhrazeno pro implementační detaily, které nemění logický stav, jako jsou vyrovnávací paměti (caches), líné načítání hodnot (lazy values), metriky nebo mutexy. Klíčové slovo `const` představuje kontrakt API týkající se mutací, nikoli automatickou záruku bezpečnosti pro vlákna (thread safety); záruky souběžnosti vyžadují samostatnou implementaci a dokumentaci.

Zkusit odpovědět na tuto otázku s trenérem AI

5Co je std::byte a jak by měly být v C++ bezpečně reprezentovány surové binární buffery?

std::byte je samostatný typ pro reprezentaci surových binárních dat jako bajtů, nikoliv jako znaků nebo aritmetických celých čísel. Zlepšuje typovou bezpečnost, protože bajtové buffery nejsou omylem považovány za textové nebo číselné hodnoty, ale stále podporují bitové operace. Surové binární buffery by měly být obvykle reprezentovány pomocí úložiště orientovaného na bajty, jako je std::vector<std::byte> nebo std::array<std::byte, N>, a předávány do rozhraní API (Application Programming Interface), která nepřebírají vlastnictví, jako std::span<std::byte> nebo std::span<const std::byte>. Serializační kód by měl hodnoty explicitně kódovat a dekódovat, místo aby spoléhal na libovolné rozložení objektů v paměti.

Zkusit odpovědět na tuto otázku s trenérem AI

Životnost objektů

6Popište Pravidlo nuly (Rule of Zero), Pravidlo tří (Rule of Three) a Pravidlo pěti (Rule of Five) a vysvětlete, kdy se každé z nich uplatňuje.

Pravidlo nuly (Rule of Zero): dávejte přednost třídám, které nedeklarují vlastní destruktor nebo operace kopírování či přesunu; nechte správu prostředků na členech využívajících RAII (Resource Acquisition Is Initialization), jako jsou `std::string`, `std::vector`, `std::unique_ptr`, obálky souborů či soketů a podobně. Pravidlo tří (Rule of Three): pokud třída ručně spravuje prostředek a potřebuje vlastní destruktor, kopírovací konstruktor (copy constructor) nebo operátor přiřazení kopírováním (copy assignment operator), obvykle potřebuje všechny tři, aby správně definovala chování při kopírování a vlastnictví. Pravidlo pěti (Rule of Five): v C++11 a novějších verzích by takové typy měly zvážit také implementaci konstruktoru přesunu a operátoru přiřazení přesunem. Pravidlo nuly používejte pro většinu aplikačních typů; Pravidlo tří/pěti používejte tehdy, když typ přímo vlastní nějaký prostředek nebo má netriviální sémantiku vlastnictví či životnosti.

Zkusit odpovědět na tuto otázku s trenérem AI

Zpracování chyb

7Vysvětlete zpracování výjimek v C++, rozvíjení zásobníku (stack unwinding), interakci s destruktory a hranice pro výjimky ve službách (service exception boundaries).

Výjimky v C++ předávají řízení z výrazu `throw` do nejbližšího odpovídajícího bloku `catch`. Během šíření výjimky proces rozvíjení zásobníku (stack unwinding) zničí plně zkonstruované automatické objekty v opačném pořadí, takže úklid prostředků na základě principu RAII (Resource Acquisition Is Initialization) proběhne automaticky. Destruktory by obecně neměly vyhazovat výjimky; pokud výjimka unikne z destruktoru označeného jako `noexcept` nebo pokud během aktivního rozvíjení zásobníku unikne další výjimka, program zavolá `std::terminate`. Výjimky by měly být obvykle odchytávány pomocí reference, typicky jako `const&`, aby se zabránilo ořezání objektu (object slicing) a zbytečnému kopírování. Backendové služby by měly definovat hranice pro výjimky, jako jsou obslužné rutiny požadavků (request handlers), vstupní body pracovních vláken, zpětná volání (callbacks) frameworků pro RPC/HTTP a funkce `main`. Na těchto místech by měly být výjimky zalogovány, převedeny na chybové odpovědi nebo stavové kódy a nesmí jim být dovoleno uniknout do nevhodných kontextů, jako jsou C API, destruktory, vlákna nebo funkce označené jako `noexcept`.

Zkusit odpovědět na tuto otázku s trenérem AI

8Co se stane, pokud destruktor vyhodí výjimku, a jak by měly backendové typy hlásit selhání při úklidu prostředků?

Destruktory jsou v běžných případech implicitně `noexcept(true)`, takže pokud z takového destruktoru unikne výjimka, je zavoláno `std::terminate`. I když je destruktor explicitně deklarován jako `noexcept(false)`, vyhození výjimky během rozbalování zásobníku (stack unwinding) je nebezpečné, protože druhá unikající výjimka v době, kdy je již jiná výjimka aktivní, rovněž ukončí program. Proto by destruktory měly provádět úklid v rámci maximální snahy (best-effort) a neměly by nechat výjimky uniknout. Backendové typy by měly selhání úklidu hlásit prostřednictvím explicitních operací, jako jsou `close()`, `flush()`, `commit()`, `stop()` nebo `shutdown()`, které vracejí chybu/`expected` nebo vyhodí výjimku před samotnou destrukcí. Destruktor může zaznamenávat do logu, odesílat metriky, potlačovat chyby nebo provádět bezpečný záložní úklid, ale neměl by být primárním kanálem pro hlášení chyb u selhání, která vyžadují reakci.

Zkusit odpovědět na tuto otázku s trenérem AI

Standardní knihovna

9Jak std::vector spravuje kapacitu, růst, realokaci a stabilitu iterátorů?

std::vector ukládá prvky souvisle (contiguously) a sleduje jak velikost (size), tak kapacitu (capacity). Velikost je počet zkonstruovaných prvků; kapacita je množství alokovaného úložiště pro prvky, které je k dispozici předtím, než je nutná další alokace. Pokud by přidání prvků překročilo kapacitu, vektor alokuje větší blok, obvykle s využitím geometrického růstu definovaného danou implementací, přesune nebo zkopíruje existující prvky, zničí ty staré a uvolní původní úložiště. reserve(n) zvyšuje kapacitu beze změny velikosti, zatímco resize(n) mění velikost konstrukcí nebo destrukcí prvků. Realokace zneplatní (invalidates) všechny iterátory, reference a ukazatele na prvky; dokonce i bez realokace mohou operace jako insert a erase zneplatnit pozice v místě modifikace nebo za ním.

Zkusit odpovědět na tuto otázku s trenérem AI

10Jaká pravidla zneplatnění (invalidation rules) byste měli znát u standardních kontejnerů se souvislou pamětí (contiguous) a těch založených na uzlech (node-based)?

Pravidla zneplatnění závisí na konkrétním kontejneru a operaci. Souvislé kontejnery, jako jsou `vector` a `string`, mají velmi křehkou stabilitu iterátorů a odkazů: růst jejich kapacity může vést k realokaci a zneplatnění všech iterátorů, odkazů i ukazatelů. Rovněž vkládání a mazání může posunout další prvky a zneplatnit tak pozice na místě změny nebo za ním i bez nutnosti realokace. Uspořádané kontejnery založené na uzlech, jako jsou `list`, `map`, `set` a jejich `multi` varianty, obecně udržují iterátory a odkazy na existující (nesmazané) prvky během vkládání stabilní; smazání prvku zneplatní iterátor či odkaz jen a pouze na daný smazaný prvek. Neuspořádané kontejnery (unordered containers) také ukládají prvky v uzlech, takže odkazy a ukazatele na prvky zůstávají při přehešování (rehash) povětšinou stabilní, avšak přehešování kompletně zneplatňuje iterátory. Kontejner `deque` má speciální pravidla spojená se segmentovaným úložištěm. V praxi si před ukládáním iterátorů nebo odkazů během jejich úprav (modifications) vždy ověřte daný kontejner a konkrétní operaci.

Zkusit odpovědět na tuto otázku s trenérem AI

11Porovnejte kontejnery `std::map`, `std::unordered_map` a kontejnery typu flat-map pro vyhledávací tabulky v backendu.

`std::map` je seřazený, obvykle na stromech založený asociativní kontejner s logaritmickou složitostí pro vyhledávání, vkládání a mazání; je užitečný, pokud záleží na seřazené iteraci, dotazech na rozsahy (range queries) nebo garanci zachování pořadí. `std::unordered_map` je kontejner založený na hashovací tabulce, s průměrně konstantní dobou pro operace nad přesným klíčem a bez zachování pořadí klíčů; často tvoří dobrou výchozí volbu pro velké, měnitelné vyhledávací tabulky za předpokladu dobré kvality hashování. Kontejner typu flat-map ukládá seřazené dvojice klíč/hodnota souvisle v paměti, což poskytuje dobrou lokálnost mezipaměti (cache locality), rychlou iteraci a vyhledávání pomocí binárního půlení, ovšem vkládání a mazání uprostřed paměťového bloku má lineární složitost. Pro vyhledávací tabulky na backendu byste měli volit podle toho, zda zátěž (workload) vyžaduje řazení či dotazy na rozsahy, převážně přesné vyhledávání, časté mutace, předvídatelnou latenci, nízkou paměťovou režii nebo zda závisí na chování mezipaměti (cache).

Zkusit odpovědět na tuto otázku s trenérem AI

12Vysvětlete `std::optional` a typické případy jeho použití v backendu pro reprezentaci chybějících hodnot.

Typ `std::optional<T>` reprezentuje buďto obsaženou hodnotu typu `T`, nebo žádnou hodnotu. Prázdný stav je reprezentován hodnotou `std::nullopt`; kód může zkontrolovat přítomnost hodnoty pomocí `has_value()` nebo použitím typu optional v booleovském kontextu, přistoupit k hodnotě pomocí operátoru `*` nebo metody `value()` a poskytnout výchozí hodnotu pomocí `value_or()`. V backendovém kódu je užitečný pro databázová pole s možností prázdné hodnoty (nullable), nepovinná pole v požadavcích nebo konfiguraci, nenalezení hodnoty (miss) v mezipaměti nebo repozitáři, kde se nepřítomnost očekává, a pro doménové stavy, u kterých by byla strážní hodnota (sentinel value) jako `-1` nebo prázdný řetězec nejednoznačná. Tento typ modeluje nepřítomnost hodnoty, nikoliv polymorfismus nebo detailní informace o chybách.

Zkusit odpovědět na tuto otázku s trenérem AI

13Co jsou `std::string_view` a `std::span` a jaká rizika týkající se životnosti objektů přinášejí nevlastnící pohledy (non-owning views)?

`std::string_view` je nevlastnící pohled (non-owning view) na souvislou posloupnost znaků; `std::span<T>` je nevlastnící pohled na souvislou posloupnost prvků typu `T`. Jsou užitečné pro parametry, které nevyžadují kopírování (zero-copy), a pro API vyrovnávacích pamětí, protože nesou ukazatel a délku bez alokace nebo vlastnictví paměti. Hlavním rizikem je životnost objektů: odkazované úložiště musí přežít (outlive) samotný pohled a nesmí být zneplatněno během používání pohledu. Vrácení nebo uložení pohledu na dočasný objekt, lokální objekt, zničený objekt nebo realokovaný kontejner může zanechat takzvaný visící pohled (dangling view).

Zkusit odpovědět na tuto otázku s trenérem AI

Souběžnost

14Jak se od sebe liší std::mutex, std::shared_mutex a std::recursive_mutex a kdy byste každý z nich zvolili?

std::mutex poskytuje exkluzivní uzamčení: může jej držet pouze jedno vlákno, takže se jedná o výchozí volbu pro ochranu sdíleného měnitelného stavu. std::shared_mutex podporuje sdílené zámky pro čtenáře a exkluzivní zámky pro zapisovatele: zámek může držet současně mnoho čtenářů, ale zapisovatelé potřebují exkluzivní přístup. Zvolte jej pro data, která se převážně čtou, kdy souběžnost čtenářů vyváží režii zámku a kdy je spravedlnost vůči zapisovatelům nebo jejich potenciální vyhladovění (starvation) akceptovatelné, případně ošetřené. std::recursive_mutex je exkluzivní mutex, který může stejné vlákno uzamknout vícekrát a musí jej odemknout stejný počet krát; používejte jej zřídka, hlavně u staršího (legacy) nebo reentrantního kódu, protože může skrývat špatný návrh zamykání.

Zkusit odpovědět na tuto otázku s trenérem AI

Vlastnosti jazyka

15Vysvětlete režimy zachytávání (capture modes) u lambda výrazů a jak tato zachytávání interagují s životností objektů (object lifetimes) ve zpětných voláních (callbacks).

Lambda výraz může zachytávat proměnné hodnotou (`[x]` nebo `[=]`), referencí (`[&x]` nebo `[&]`), může zachytávat `this`, nebo použít inicializační zachytávání (init-capture), jako např. `[p = std::move(ptr)]`. Zachytávání hodnotou zkopíruje zachycený objekt do uzávěru (closure) v okamžiku vytvoření lambdy; zachytávání referencí ukládá reference, takže původní objekty musí přežít všechna volání lambdy. Ve zpětných voláních (callbacks), uložených lambdách nebo při asynchronní práci jsou zachytávání referencí a zachytávání `this` nebezpečné, protože lokální proměnné nebo samotný objekt mohou být zničeny ještě před zavoláním lambdy. Dávejte přednost zachytávání potřebných dat hodnotou, přesunu či inicializačnímu zachycení (move/init-capture) pro předání vlastnictví, nebo cílenému použití vzorů s `shared_ptr`/`weak_ptr`, pokud je nutné prodloužit či ověřit životnost objektu.

Zkusit odpovědět na tuto otázku s trenérem AI