Forberedelse til C++ interview

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

Udvalgte C++ interviewspørgsmål til backend-udviklere, inddelt efter emne og hentet fra det samme spørgsmålskatalog, som driver EngineerSpeak-platformen.

Start et AI-baseret C++ backend-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

3Hvordan fungerer brugerdefinerede deleters i smart pointers, og hvornår er de nyttige for C/C++ interoperabilitet?

En brugerdefineret deleter er kaldbart oprydningslogik, som bruges af en smart pointer i stedet for standard delete-operationen, når pointeren frigiver sin ejede ressource. Det er nyttigt for C/C++ interoperabilitet, når en ressource skal frigives med en specifik funktion som fclose, close, curl_easy_cleanup, SSL_free, free eller en biblioteks-nedrivningsfunktion. For unique_ptr er deleter-typen en del af unique_ptr-typen og kan påvirke dens størrelse; tilstandsløse deleters kan optimeres væk, mens funktionspointere eller tilstandsfyldte deleters tilføjer lagerplads. For shared_ptr lagres deleteren i kontrolblokken og køres, når den sidste stærke ejer frigiver objektet. Brugerdefinerede deleters lader C-ressourcer indgå sikkert i RAII (Resource Acquisition Is Initialization).

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

4Forklar kontrolblok-mekanikken bag `weak_ptr` og `shared_ptr`, herunder cyklusser, `enable_shared_from_this` og omkostninger ved referenceoptælling.

En `shared_ptr` håndterer delt ejerskab via en kontrolblok, der indeholder stærke og svage referenceoptællinger samt oprydningsinformation såsom slettefunktionen og allokatoren. Kopiering eller destruktion af `shared_ptr`-objekter tæller den stærke reference op eller ned, typisk med atomare operationer, så separate `shared_ptr`-objekter kan manipuleres sikkert på tværs af tråde, men hver opdatering af referenceoptællingen har en omkostning. Når den stærke optælling når nul, destrueres det administrerede objekt; kontrolblokken forbliver, indtil de svage referencer også er væk. En `weak_ptr` peger på den samme kontrolblok uden at forlænge objektets levetid; `lock()` returnerer en `shared_ptr`, hvis objektet stadig er i live, og ellers en tom `shared_ptr`. Cyklusser, der kun består af `shared_ptr`, forårsager hukommelseslækager, fordi de stærke optællinger aldrig når nul, så `weak_ptr` bruges til bagud-pointere eller observer-links. `enable_shared_from_this` lader et objekt, der allerede ejes af en `shared_ptr`, oprette en ny `shared_ptr` til sig selv ved hjælp af den eksisterende kontrolblok, hvilket undgår farlige, separate kontrolblokke. Omkostninger ved referenceoptælling inkluderer atomare op-/nedtællinger, cachekonflikter, allokering af kontrolblokke samt overhead i kodeområder, der er kritiske for ydeevnen.

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

Typesystem

5Hvad 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

6Beskriv 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

7Hvad er typer, der kun kan flyttes (move-only types), og hvordan påvirker de API-grænser (Application Programming Interface) for sockets, filhåndtag og låse?

Move-only-typer er typer, der ikke kan kopieres, men som kan flyttes. De er almindelige til unikt ejerskab af ressourcer såsom sockets, fildeskriptorer, filhåndtag, låse og `std::unique_ptr`. Kopieringsoperationer er slettet for at undgå duplikeret ejerskab og dobbelt frigivelse; flytteoperationer (move) overdrager ejerskabet og efterlader kilden i en gyldig, men normalt tom eller ikke-ejende tilstand. API'er bør gøre grænser for ejerskab eksplicitte: Fabrikker (factories) kan returnere move-only-objekter som værdier, funktioner, der overtager ejerskabet, kan acceptere dem som værdier eller rvalue-referencer, og funktioner, der kun inspicerer, bør tage imod referencer, pointere eller andre typer, der blot låner ressourcen. Containere kan gemme move-only-værdier, når elementer indsættes eller omplaceres ved hjælp af flytning (move).

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

8Sammenlign `auto`, `decltype`, `decltype(auto)` og udledning af skabelonargumenter (template argument deduction) i typisk backend-kode.

`auto` bruger skabelonlignende udledning (template deduction) til variabler: Simpel `auto` kasserer normalt referencer og `const` på topniveau, medmindre deklarationen beder om dem, såsom `auto&`, `const auto&` eller `auto&&`. `decltype(expr)` undersøger den deklarerede type eller udtrykkets type mere præcist: Et id-udtryk uden parenteser giver den deklarerede type, mens andre lvalue-udtryk producerer `T&`, xvalues producerer `T&&`, og prvalues producerer `T`. `decltype(auto)` udleder typen ved hjælp af `decltype`-reglerne, hvilket ofte bruges til returtyper, hvor referencer skal bevares. Udledning af skabelonargumenter ligner `auto`, men afhænger af parameterformen, som f.eks. `T`, `T&`, `const T&` eller `T&&`, og har sine egne regler. Initialisering med tuborgklammer er en typisk forskel: `auto x = {1,2}` udleder `std::initializer_list<int>`, hvorimod en almindelig skabelonparameter generelt ikke kan udlede `T` fra en ren initialisering med tuborgklammer, medmindre parameteren forventer en `initializer_list` eller en anden passende type.

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

Objekters levetid

9Beskriv "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

Sprogsemantik

10Beskriv C++-initialiseringsformer og almindelige faldgruber, herunder initializer_list og aggregater.

C++ har adskillige initialiseringsformer. Standardinitialisering (default initialization), såsom `T x;`, kalder en standardkonstruktør for klassetyper, men efterlader automatiske fundamentale variabler uinitialiserede. Værdiinitialisering (value initialization), såsom `T x{};` eller `T()`, nulstiller (zero-initializes) hvor det er relevant, før konstruktør- eller medlemsinitialisering. Listeinitialisering bruger tuborgklammer, afviser indsnævrende konverteringer (narrowing conversions) og har særlige regler for overload-opløsning, herunder en stærk præference for brugbare `std::initializer_list`-konstruktører. Aggregatinitialisering initialiserer aggregatmedlemmer direkte med tuborgklammer; C++20 understøtter også udpegede initialisatorer (designated initializers) til aggregater i deklarationsrækkefølge. Almindelige faldgruber inkluderer uinitialiserede lokale skalarer, overraskende valg af `initializer_list`-overloads, indsnævringsfejl ved brug af tuborgklammer, fænomenet "the most vexing parse" med parenteser, og adfærdsændringer, når en type ophører med at være et aggregat.

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

11Hvad er udefineret adfærd (undefined behavior) i C++, og hvordan kan det komme til udtryk i backend-hændelser i produktion?

Udefineret adfærd er adfærd, for hvilken C++-standarden ikke stiller nogen krav, efter at en ugyldig operation har fundet sted. Programmet kan tilsyneladende fungere, crashe, korrumpere data, afsløre sikkerhedsfejl eller blive optimeret til overraskende adfærd. Compilere antager, at UB (Undefined Behavior) ikke forekommer, og de optimerer baseret på denne antagelse, så problemer dukker muligvis kun op i release-builds eller under produktionstrafik. Backend-hændelser kan stamme fra hængende pointere/referencer (dangling pointers/references), brug-efter-frigivelse (use-after-free), overtrædelser af objekters levetid, tilgang uden for grænserne (out-of-bounds access), overflow af fortegnede heltal, dataræs (data races), ugyldige typekonverteringer (invalid casts), læsning af uinitialiserede data, dobbeltfrigivelse (double frees) eller overtrædelser af strict-aliasing. Mitigering omfatter RAII (Resource Acquisition Is Initialization) og et klart design af ejerskab og levetid, sikrere abstraktioner og grænsekontrol, test og fuzzing, kodegennemgang, statisk analyse samt sanitizers som ASan, UBSan og TSan.

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

Object Model

12Forklar virtuelle funktioner, vtables, omkostningerne ved dynamisk afsendelse (dynamic dispatch), objektskæring (object slicing) og farerne ved virtuelle destruktorer.

En virtuel funktion muliggør polymorfi ved kørselstid: når den kaldes gennem en pointer eller reference til en basisklasse, vælges implementeringen for objektets dynamiske type. De fleste implementeringer gemmer en skjult vptr i hvert polymorft objekt, som peger på en vtable af virtuelle funktionsadresser for den specifikke dynamiske type. Den typiske omkostning er en ekstra pointer i objektet, et indirekte kald, mulig påvirkning af cache og forudsigelse af forgreninger (branch prediction), samt færre muligheder for inlining, selvom compilere nogle gange kan devirtualisere kaldet. Objektskæring (object slicing) opstår, når et nedarvet objekt kopieres eller gemmes som en værdi (by value) i form af et basisklasse-objekt, hvorved den nedarvede del og den dynamiske adfærd går tabt. Hvis det er meningen, at en basisklasse skal slettes gennem en basisklasse-pointer, skal dens destruktor være virtuel; ellers vil sletning af et nedarvet objekt gennem den pågældende pointer resultere i udefineret adfærd (undefined behavior).

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

Standardbiblioteket

13Beskriv `std::chrono`-ure, tidspunkter (`time_point`-objekter) og varigheder (`duration`-objekter) til service-timeouts, metrikker og tidsstempler.

`std::chrono` modellerer tid med ure, `time_point`-objekter og `duration`-objekter. En `duration` er et interval med en enhed, såsom millisekunder eller sekunder. Et `time_point` er et punkt på et specifikt urs tidslinje. `steady_clock` er monotonisk og bør bruges til forløbet tid, service-timeouts, deadlines og latensmålinger, fordi det ikke påvirkes af ændringer i realtiden (wall-clock changes). `system_clock` repræsenterer almindelig tid/realtid og er velegnet til tidsstempler, logning, persistens og kalenderkonvertering, men den kan hoppe, når systemtiden justeres. Timeouts bør som regel baseres på `steady_clock::now() + duration`; maskintidsstempler bør bruge et tydeligt dokumenteret format for systemtid, typisk UTC, ved API- og lagringsgrænser.

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

14Beskriv `std::format` og moderne formateringsfaciliteter sammenlignet med iostreams og API'er (Application Programming Interfaces) i `printf`-stil.

`std::format` er den typesikre formateringsfacilitet i C++20, som er inspireret af fmtlib. Den bruger `{}`-erstatningsfelter og formateringsspecifikationer til at producere formateret tekst uden iostreams' tilstandsbaserede indsættelsessyntaks eller C-varargs i `printf`-stil. Sammenlignet med `printf` undgår den mange problemer med uoverensstemmelse mellem format og type; sammenlignet med iostreams er den ofte tydeligere og lettere at sammensætte. fmtlib er det udbredte bibliotek, der gik forud for og påvirkede `std::format`, og som kan tilbyde bredere understøttelse eller nyere funktioner. Brugerdefinerede typer kan formateres ved hjælp af understøttelse fra tilpassede formateringsværktøjer (custom formatters). Ved logning i store mængder afhænger ydeevnen af at undgå unødvendig formatering, konvertering og allokering, især for deaktiverede logniveauer; API'er, der udskyder formatering eller kontrollerer logniveauet først, foretrækkes.

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

Templates

15Forklar værdikategorier ("value categories") og "perfect forwarding" i C++, og hvorfor de er vigtige for effektive generiske API'er.

Værdikategorier i C++ beskriver udtryk: lvalues har en identitet og kan refereres til efter udtrykket; prvalues er rene rvalues som f.eks. mange midlertidige eller beregnede værdier; xvalues er udløbende objekter ("expiring objects"), hvis ressourcer kan genbruges. "Perfect forwarding" er en template-teknik, hvor man tager en forwarding-reference, typisk `T&&` hvor `T` udledes, og videresender med `std::forward<T>(arg)`, så kalderens værdikategori bevares: lvalues forbliver lvalues, og rvalues forbliver rvalues. Reglerne for "reference collapsing" gør dette muligt. Det har betydning for generiske backend-API'er (Application Programming Interface), fordi wrappers, fabrikker ("factories"), dispatchere og emplace-lignende funktioner kan undgå unødvendige kopier og samtidig bevare valg af overload samt move-adfærd.

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

Samtidighed

16Beskriv strategier til forebyggelse af deadlock og låsning af flere mutexes ved hjælp af `std::scoped_lock` og `std::lock`.

Deadlock forhindres ved at undgå cirkulær ventetid (circular waits): erhverv låse i en konsekvent global rækkefølge, når det er muligt, eller erhverv flere mutexes med `std::lock`/`std::scoped_lock`, som bruger en algoritme til undgåelse af deadlock. `std::scoped_lock lock(a, b, ...)` er den enkleste RAII-form (Resource Acquisition Is Initialization) til at låse adskillige mutexes og automatisk låse dem op, når synlighedsområdet forlades. Med `std::lock` låser man først sine mutexes og tilføjer derefter RAII-indpakninger ved hjælp af `std::adopt_lock`, eller man kan bruge `std::unique_lock` med `std::defer_lock`. Hold kritiske sektioner korte, og undgå blokerende operationer eller ukendte callbacks, mens der holdes låse; undgåelse af deadlock garanterer ikke automatisk retfærdighed (fairness) og forhindrer ikke alle scenarier med livelock eller ressourceudsultning (starvation).

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

17Forklar brugsmønstre for condition_variable til at vente på tilstandsændringer.

Brug `std::condition_variable` til at vente på, at et prædikat for en tilstand, der er beskyttet af en mutex, ændres. Den delte tilstand ændres, mens den samme mutex holdes, hvorefter `notify_one` eller `notify_all` vækker de tråde, der venter. Tråde, der venter, bør bruge `cv.wait(lock, predicate)` eller en tilsvarende løkke, da der kan forekomme falske opvågninger, og notifikationer ikke huskes uafhængigt af prædikatets tilstand. `notify_one` vækker én ventende tråd; `notify_all` vækker alle ventende tråde og er velegnet til at udsende tilstandsændringer bredt, som for eksempel ved nedlukning.

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

18Design en trådsikker, afgrænset kø (bounded queue) ved hjælp af standard C++-primitiver og definér dens API-garantier (Application Programming Interface).

En afgrænset, trådsikker kø kan bygges med en `std::mutex`, betingelsesvariabler (condition variables), en container med fast kapacitet og et flag for nedlukning/lukket tilstand. `push` blokerer, timer ud eller fejler, når køen er fuld, hvilket skaber modtryk (backpressure); `pop` blokerer, når køen er tom. Begge operationer bør vente på prædikater såsom `size < capacity || closed` og `!empty || closed`. Ved nedlukning eller lukning vækkes blokerede producenter og forbrugere; nye kald af `push` afvises, og det defineres, om forbrugere tømmer eksisterende elementer eller stopper øjeblikkeligt. API'et bør specificere blokeringsadfærd, lukkemekanismer, returværdier og garantier for samtidighed.

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

Database

19Hvordan udfører man en skemamigrering for en C++-tjeneste uden nedetid?

Brug en trinvis udvid-og-sammentræk-migrering (expand-contract). Foretag først bagudkompatible skematilføjelser, såsom kolonner, der tillader null, eller nye tabeller, som ikke ødelægger den kørende C++-tjeneste. Udrul kompatibel kode, der kan håndtere både gamle og nye repræsentationer, efterfyld (backfill) eksisterende data i små portionsvise bidder med hastighedsbegrænsning (throttled batches), valider konsistensen, omskift læsninger til det nye skema, og fjern først senere gamle kolonner eller kode, når ingen udrullede versioner længere afhænger af dem. For store tabeller bør man undgå lange, blokerende DDL-operationer (Data Definition Language), bruge online/samtidige operationer, hvor det understøttes, overvåge låse/replikering/ressourcer og bevare tilbageførselsveje (rollback paths), der fungerer med delvist udrullet kode og delvist migreret data.

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

20Hvordan bør en C++-tjeneste læse fra database-replikaer, mens den håndterer forældede læsninger (stale reads) og "read-your-writes"-garantier?

Behandl læsninger fra replikaer som et eksplicit kompromis med konsistensen. Læsninger, der kan tåle at være forældede, kan sendes til sunde replikaer. Omvendt bør "read-your-writes", transaktionelle eller kritisk opdaterede læsninger sendes til primærdatabasen, medmindre man ved, at den valgte replika har afspillet den relevante skriveposition, såsom et LSN (Log Sequence Number), et tidsstempel eller en bestemt version. Overvåg replika-forsinkelse og sundhedstilstand, indkod det krævede konsistensniveau pr. endpoint eller forespørgsel, og fald tilbage til primærdatabasen, vent på indhentning eller fejl, når replikaerne overstiger den tilladte forældelse. Dokumenter konsistensmodellen, så kaldere ved, hvilke læsninger der kan være forældede.

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