Forberedelse til Junior C++

Interviewspørgsmål til Junior C++ Backend-udviklere

15 udvalgte interviewspørgsmål til Junior C++ backend-udviklere, der tydeligt skal kunne forklare sprogets grundprincipper og basalt ejerskab.

Start et AI-baseret Junior C++ interviewIntet kreditkort kræves. 1 gratis session er tilgængelig.
Øv tekniske jobsamtaler på engelskEn tilstand, hvor ikke-modersmålstalende kan øve sig i at bestå tekniske interviews.

Resource Management

1Forklar RAII (Resource Acquisition Is Initialization) og hvordan det former undtagelsessikker ressourcehåndtering i C++ backend-tjenester.

RAII (Resource Acquisition Is Initialization) betyder, at et C++-objekt ejer en ressource og frigiver den fra sin destruktor. Fordi lokale objekter destrueres automatisk, når deres levetid slutter, hvilket også gælder under `stack unwinding` ved undtagelser (exceptions), giver RAII deterministisk oprydning og gør fejlhåndtering undtagelsessikker. I backend-tjenester gælder dette ikke kun for hukommelse, men også for file descriptors, sockets, mutex-låse, database-handles, transaktioner og andre OS- eller applikationsressourcer.

Prøv at besvare dette spørgsmål med en AI-coach

Hukommelseshåndtering

2Sammenlign `std::unique_ptr` og `std::shared_ptr`, og beskriv, hvornår de hver især er velegnede i backend-API'er (Application Programming Interfaces).

`std::unique_ptr` repræsenterer eksklusivt ejerskab: den er billig, kan flyttes, men ikke kopieres, og er velegnet til en enkelt ejer eller til API'er, der overdrager ejerskabet. `std::shared_ptr` repræsenterer delt ejerskab: den kan kopieres og holder et objekt i live via referenceoptælling, indtil den sidste stærke ejer frigiver det. I backend-API'er bør du bruge `unique_ptr`, når ejerskabet overdrages, `shared_ptr` kun når flere uafhængige ejere er nødt til at forlænge levetiden, og referencer eller rå pointere til adgang uden ejerskab. `std::make_shared` foretrækkes normalt ved oprettelse af `shared_ptr`-objekter, da det er effektivt og exceptionsikkert.

Prøv at besvare dette spørgsmål med en AI-coach

Typesystem

3Hvad er move-semantik, og hvordan implementerer du korrekt move-konstruktion og move-tildeling?

Move-semantik giver C++ mulighed for at overføre ressourcer fra midlertidige eller på anden vis undværlige objekter i stedet for at kopiere dem. Det bruger rvalue-referencer såsom `T&&` og `std::move`, hvilket er et typecast, der tillader, at move-overloads bliver valgt; `std::move` flytter ikke i sig selv noget. En korrekt move-konstruktør initialiserer et nyt objekt ved at overtage kildeobjektets ressource og efterlade kilden i en gyldig tilstand, der kan destrueres og tildeles til. En korrekt move-tildelingsoperator overfører til et eksisterende objekt, håndterer eller tolererer selv-tildeling ("self-assignment"), frigiver eller genbruger destinationens nuværende ressource, overtager kilderessourcen og efterlader kilden i en sikker tilstand. Move-operationer bør ofte være `noexcept`, så standardcontainere kan bruge dem under genallokering og samtidig bevare undtagelsesgarantier ("exception guarantees").

Prøv at besvare dette spørgsmål med en AI-coach

4Beskriv const-correctness i C++ API'er (Application Programming Interfaces), og hvordan man effektivt designer `const`-medlemsfunktioner.

Const-correctness betyder, at man gennem typesystemet udtrykker, hvilke operationer der ikke ændrer et objekts observerbare eller logiske tilstand. En `const`-medlemsfunktion har et `const`-kvalificeret `this`-objekt, så den kan ikke ændre ikke-`mutable` datafelter eller kalde ikke-`const`-medlemsfunktioner på det samme objekt. Godt API-design markerer skrivebeskyttede forespørgsler som `const`, returnerer værdier eller `const`-referencer/pointere, når det er relevant, og undgår at eksponere foranderlig intern tilstand fra `const`-funktioner. Nøgleordet `mutable` bør reserveres til implementeringsdetaljer, der ikke ændrer den logiske tilstand, såsom caches, lazy-evaluerede værdier, metrikker eller mutexer. `const` er en API-kontrakt om mutering, ikke en automatisk garanti for trådsikkerhed; samtidighedsgarantier kræver separat implementering og dokumentation.

Prøv at besvare dette spørgsmål med en AI-coach

5Hvad er std::byte, og hvordan bør rå binære buffere repræsenteres sikkert i C++?

std::byte er en særskilt type til at repræsentere rå binære data som bytes, ikke som tegn eller aritmetiske heltal. Den forbedrer typesikkerheden, fordi byte-buffere ikke ved et uheld behandles som tekst eller numeriske værdier, samtidig med at den stadig understøtter bitvise operationer. Rå binære buffere bør normalt repræsenteres med byte-orienteret lagring såsom std::vector<std::byte> eller std::array<std::byte, N>, og overføres via ikke-ejende API'er (Application Programming Interfaces) som std::span<std::byte> eller std::span<const std::byte>. Serialiseringskode bør eksplicit indkode og afkode værdier i stedet for at bero på vilkårlige objektstrukturer.

Prøv at besvare dette spørgsmål med en AI-coach

Objekters levetid

6Beskriv "Rule of Zero", "Rule of Three" og "Rule of Five", og hvornår hver enkelt anvendes.

Rule of Zero: Foretræk klasser, der ikke deklarerer brugerdefinerede operationer for destruktor, kopiering eller flytning (move); lad medlemmer baseret på RAII (Resource Acquisition Is Initialization) som `std::string`, `std::vector`, `std::unique_ptr`, indpakninger til filer/sockets osv. håndtere ressourcer. Rule of Three: Hvis en klasse manuelt håndterer en ressource og har brug for en brugerdefineret destruktor, kopikonstruktør eller kopitildelingsoperator, har den som regel brug for alle tre for at definere korrekt adfærd for kopiering og ejerskab. Rule of Five: I C++11 og nyere versioner bør sådanne typer også overveje en flyttekonstruktør og en flyttetildelingsoperator. Brug Rule of Zero til de fleste applikationstyper; brug Rule of Three/Five, når typen direkte ejer en ressource eller har ikke-triviel semantik for ejerskab og levetid.

Prøv at besvare dette spørgsmål med en AI-coach

Fejlhåndtering

7Forklar C++-undtagelseshåndtering (exception handling), stakudrulning (stack unwinding), interaktion med destruktorer og grænser for undtagelser i tjenester (service exception boundaries).

C++-undtagelser overfører kontrollen fra et `throw`-udtryk til den nærmeste matchende `catch`. Under propageringen destruerer stakudrulningen (stack unwinding) fuldt konstruerede automatiske objekter i omvendt rækkefølge, så oprydning via RAII (Resource Acquisition Is Initialization) kører automatisk. Destruktorer bør generelt ikke kaste undtagelser; hvis en undtagelse undslipper en `noexcept`-destruktor, eller en anden undtagelse kastes under en igangværende stakudrulning, kalder programmet `std::terminate`. Undtagelser bør normalt fanges via reference, typisk `const&`, for at undgå slicing og unødvendige kopier. Backend-tjenester bør definere grænser for undtagelser (exception boundaries), såsom forespørgselshåndterere, startpunkter for worker-tråde, callbacks i RPC/HTTP-frameworks (Remote Procedure Call / Hypertext Transfer Protocol) og `main`, hvor undtagelser logges, konverteres til fejlsvar eller statuskoder og forhindres i at undslippe til upassende kontekster som f.eks. C-API'er (Application Programming Interfaces), destruktorer, tråde eller `noexcept`-funktioner.

Prøv at besvare dette spørgsmål med en AI-coach

8Hvad sker der, hvis en destruktor kaster en undtagelse (throw), og hvordan bør backend-typer rapportere fejl ved oprydning?

Destruktorer er implicit `noexcept(true)` i normale tilfælde, så hvis en undtagelse slipper ud af en sådan destruktor, kaldes `std::terminate`. Selvom en destruktor eksplicit erklæres som `noexcept(false)`, er det farligt at kaste en undtagelse under stack-unwinding, da endnu en undtagelse, der slipper ud, mens en anden undtagelse allerede er aktiv, også vil afslutte programmet. Derfor bør destruktorer udføre oprydning efter bedste evne og ikke tillade undtagelser at slippe ud. Backend-typer bør i stedet rapportere fejl ved oprydning gennem eksplicitte handlinger som `close()`, `flush()`, `commit()`, `stop()` eller `shutdown()`, der enten returnerer en fejl/`expected` eller kaster en undtagelse før destruktion. Destruktoren kan logge, udsende metrikker, undertrykke fejl eller udføre sikker reserveoprydning, men den bør ikke være den primære kanal til at rapportere fejl for hændelser, der kræver handling.

Prøv at besvare dette spørgsmål med en AI-coach

Standardbiblioteket

9Hvordan håndterer `std::vector` kapacitet, vækst, genallokering og iterator-stabilitet?

`std::vector` gemmer elementer i et sammenhængende hukommelsesområde og holder styr på både størrelse og kapacitet. Størrelsen (`size`) er antallet af konstruerede elementer; kapaciteten (`capacity`) er den mængde allokeret lagerplads til elementer, der er tilgængelig, før en ny allokering er nødvendig. Når tilføjelsen af elementer vil overskride kapaciteten, allokerer `vector` en større blok, typisk ved brug af en implementeringsdefineret geometrisk vækststrategi, flytter eller kopierer eksisterende elementer, destruerer de gamle og frigiver det gamle hukommelsesområde. `reserve(n)` øger kapaciteten uden at ændre størrelsen, mens `resize(n)` ændrer størrelsen ved at konstruere eller destruere elementer. Genallokering ugyldiggør alle iteratorer, referencer og pointere til elementer; selv uden genallokering kan operationer som `insert` og `erase` ugyldiggøre positioner ved eller efter ændringspunktet.

Prøv at besvare dette spørgsmål med en AI-coach

10Hvilke invalideringsregler bør du kende for sammenhængende (contiguous) og node-baserede standardcontainere?

Invalideringsregler afhænger af containeren og operationen. Sammenhængende containere som `vector` og `string` har skrøbelig stabilitet for iteratorer og referencer: Vækst kan medføre reallokering og invalidere alle iteratorer, referencer og pointere, og indsættelse/sletning kan forskyde elementer og invalidere positioner ved eller efter ændringen, selv uden reallokering. Node-baserede ordnede containere som `list`, `map`, `set` og deres multi-varianter holder generelt iteratorer og referencer til eksisterende, ikke-slettede elementer stabile under indsættelse; sletning af et element invaliderer iteratoren/referencen til det pågældende slettede element. Uordnede containere (unordered containers) gemmer også elementer i noder, så referencer og pointere til elementer er generelt stabile under rehash, men rehash invaliderer iteratorer. `deque` har særlige regler for segmenteret lagring. I praksis bør du tjekke den specifikke container og operation, før du gemmer iteratorer eller referencer på tværs af modifikationer.

Prøv at besvare dette spørgsmål med en AI-coach

11Sammenlign `std::map`, `std::unordered_map` og flat-map-lignende beholdere til opslagstabeller i backend-udvikling.

`std::map` er en sorteret, oftest træbaseret associativ beholder med logaritmisk tid til opslag, indsættelse og sletning; den er nyttig, når sorteret gennemløb (iteration), intervalforespørgsler (range queries) eller garantier om rækkefølge er vigtige. `std::unordered_map` er baseret på en hashtabel med gennemsnitlig konstant tid for operationer med eksakte nøgler og uden sortering af nøglerne; det er ofte et godt standardvalg til store, foranderlige opslagstabeller, når hashingen er effektiv. En flat-map-lignende beholder gemmer sorterede nøgle/værdi-par sammenhængende i hukommelsen, hvilket giver god cache-lokalitet og hurtigt gennemløb samt opslag med binær søgning, men indsættelse og sletning i midten af beholderen tager lineær tid. Til opslagstabeller i backend-udvikling bør man vælge ud fra, om arbejdsbyrden kræver sortering/intervaller, primært eksakte opslag, hyppige ændringer, forudsigelig latenstid, hukommelses-overhead eller en bestemt cache-adfærd.

Prøv at besvare dette spørgsmål med en AI-coach

12Forklar `std::optional` og typiske backend-anvendelsestilfælde til at repræsentere fraværende værdier.

`std::optional<T>` repræsenterer enten en indeholdt `T`-værdi eller ingen værdi. Den tomme tilstand repræsenteres af `std::nullopt`. I koden kan man kontrollere `has_value()` eller bruge den optionale værdi i en boolsk kontekst, tilgå værdien med `*` eller `value()`, og angive en standardværdi med `value_or()`. I backend-kode er det nyttigt til databasefelter, der kan være null, valgfrie forespørgsels- eller konfigurationsfelter, cache- eller database-missere, hvor fravær forventes, samt domænetilstande, hvor en markørværdi (sentinel value) som `-1` eller en tom streng ville være tvetydig. Det modellerer fraværet af en værdi, ikke polymorfi eller detaljeret fejlinformation.

Prøv at besvare dette spørgsmål med en AI-coach

13Hvad er `std::string_view` og `std::span`, og hvilke farer for objekters levetid (lifetime) introducerer views uden ejerskab (non-owning views)?

`std::string_view` er et view uden ejerskab af en sammenhængende tegnsekvens; `std::span<T>` er et view uden ejerskab af en sammenhængende sekvens af `T`. De er nyttige til zero-copy-parametre og buffer-API'er, fordi de indeholder en pointer og en længde uden at allokere eller eje hukommelse. Den primære fare relaterer sig til levetid: den refererede hukommelse skal leve længere end dit view og må ikke ugyldiggøres, mens viewet bruges. Hvis man returnerer eller gemmer et view, der peger på et midlertidigt objekt, et lokalt objekt, et destrueret objekt eller en reallokeret container, kan det resultere i et hængende view (dangling view).

Prøv at besvare dette spørgsmål med en AI-coach

Samtidighed

14Hvordan adskiller `std::mutex`, `std::shared_mutex` og `std::recursive_mutex` sig fra hinanden, og hvornår ville du vælge hver enkelt?

`std::mutex` giver eksklusiv låsning: kun én tråd kan holde den, så det er standardvalget til at beskytte delt foranderlig tilstand. `std::shared_mutex` understøtter delte læselåse og eksklusive skrivelåse: mange læsere kan holde låsen samtidigt, men skrivere kræver eksklusiv adgang. Vælg den til data, der primært læses, når samtidighed for læsere opvejer overhead, og når retfærdighed for skrivere eller udsultning (starvation) af skrivere er acceptabelt eller håndteres. `std::recursive_mutex` er en eksklusiv mutex, som den samme tråd kan låse flere gange og skal låse op det samme antal gange; brug den sjældent, primært til legacy-kode eller re-entrant kode, da den kan skjule dårligt låsedesign.

Prøv at besvare dette spørgsmål med en AI-coach

Sprogfunktioner

15Forklar capture-tilstande for lambdaer, og hvordan de interagerer med objekters levetid i callbacks.

En lambda kan fange (capture) variabler som værdi (`[x]` eller `[=]`), som reference (`[&x]` eller `[&]`), fange `this` eller bruge init-capture som f.eks. `[p = std::move(ptr)]`. Værdicaptures kopierer det fangne objekt ind i closure-objektet, når lambdaen oprettes; referencecaptures gemmer referencer, så de oprindelige objekter skal leve længere end alle lambda-kald. I callbacks, gemte lambdaer eller asynkront arbejde er referencecaptures og `this`-captures farlige, fordi lokale variabler eller selve objektet kan blive destrueret før kaldet. Foretræk værdicaptures for nødvendige data, move/init-capture til at overdrage ejerskab, eller bevidste mønstre med `shared_ptr`/`weak_ptr`, når et objekts levetid skal forlænges eller kontrolleres.

Prøv at besvare dette spørgsmål med en AI-coach