C++-haastatteluun valmistautuminen

C++ Backend -kehittäjän haastattelukysymykset

Valikoituja C++-haastattelukysymyksiä backend-kehittäjille aihepiireittäin jaoteltuna. Kysymykset on kerätty samasta kysymyspankista, joka toimii EngineerSpeakin harjoitusalustan taustalla.

Aloita tekoälyavusteinen C++ Backend -haastatteluEi vaadi luottokorttia. 1 ilmainen sessio saatavilla.
Teknisten haastattelujen harjoittelu englanniksiTila, jossa ei-äidinkieliset puhujat voivat harjoitella teknisen haastattelun läpäisemistä.

Resource Management

1Selitä RAII (Resource Acquisition Is Initialization) ja miten se tukee poikkeusturvallista resurssienhallintaa C++-taustapalveluissa.

RAII (Resource Acquisition Is Initialization) tarkoittaa, että C++-olio omistaa resurssin ja vapauttaa sen destruktorissaan. Koska lokaalit oliot tuhotaan automaattisesti, kun niiden elinkaari tai näkyvyysalue päättyy – myös silloin, kun poikkeuksia käsitellään ja pinoa puretaan (stack unwinding) – RAII tarjoaa deterministisen siivouksen ja tekee virhepoluista poikkeusturvallisia. Taustapalveluissa tämä ei koske vain muistia, vaan myös tiedostokahvoja (file descriptors), verkkosocketeita, mutex-lukkoja, tietokantakahvoja, transaktioita ja muita käyttöjärjestelmän tai sovelluksen resursseja.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Muistinhallinta

2Vertaile osoittimia `std::unique_ptr` ja `std::shared_ptr` ja kuvaile, milloin kumpaakin on asianmukaista käyttää taustajärjestelmien API-rajapinnoissa (Application Programming Interface).

`std::unique_ptr` edustaa yksinomaista omistajuutta: se on kevyt, siirrettävä mutta ei kopioitava, ja se sopii yksittäiselle omistajalle tai API-rajapinnoille, jotka siirtävät omistajuuden. `std::shared_ptr` edustaa jaettua omistajuutta: se on kopioitava ja pitää olion elossa viitelaskennan avulla, kunnes viimeinen vahva omistaja vapauttaa sen. Taustajärjestelmien rajapinnoissa kannattaa käyttää `unique_ptr`-osoitinta, kun omistajuus siirretään, `shared_ptr`-osoitinta vain silloin, kun useiden itsenäisten omistajien on pidennettävä elinkaarta, ja viittauksia tai paljaita osoittimia (raw pointer) sellaiseen käyttöön, johon ei liity omistajuutta. Funktiota `std::make_shared` suositaan yleensä `shared_ptr`-olioita luotaessa, koska se on tehokas ja poikkeusturvallinen.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

3Miten kustomoidut purkajat (custom deleters) toimivat älykkäissä osoittimissa, ja milloin niistä on hyötyä C/C++ -yhteentoimivuudessa?

Kustomoitu purkaja on kutsuttava siivouslogiikka, jota älykäs osoitin käyttää oletusarvoisen `delete`-operaation sijaan vapauttaessaan omistamansa resurssin. Siitä on hyötyä C/C++ -yhteentoimivuudessa silloin, kun resurssi on vapautettava tietyllä funktiolla, kuten `fclose`, `close`, `curl_easy_cleanup`, `SSL_free`, `free` tai jollakin kirjastokohtaisella tuhoamisfunktiolla. `unique_ptr`:n tapauksessa purkajan tyyppi on osa `unique_ptr`:n tyyppiä ja voi siten vaikuttaa sen muistinkulutukseen; tilattomat purkajat voidaan optimoida pois, kun taas funktio-osoittimet tai tilalliset purkajat vaativat lisätilaa. `shared_ptr`:n kohdalla purkaja tallennetaan ohjauslohkoon (control block) ja se suoritetaan, kun viimeinen vahva omistaja vapauttaa olion. Kustomoitujen purkajien avulla C-resurssit voivat osallistua turvallisesti RAII (Resource Acquisition Is Initialization) -malliin.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

4Selitä `weak_ptr`- ja `shared_ptr`-älyosoittimien ohjauslohkon (control block) toimintamekanismit, mukaan lukien syklit, `enable_shared_from_this` ja viitelaskureiden kustannukset.

`shared_ptr` hallitsee jaettua omistajuutta ohjauslohkon (control block) kautta, joka sisältää vahvat ja heikot viitelaskurit sekä siivoustietoja, kuten purkajan (deleter) ja varaajan (allocator). `shared_ptr`-olioiden kopiointi tai tuhoaminen kasvattaa tai pienentää vahvaa viitelaskuria. Tämä tapahtuu tyypillisesti atomisilla operaatioilla, joten erillisiä `shared_ptr`-olioita voidaan käsitellä turvallisesti useissa säikeissä, mutta jokaisella viitelaskurin päivityksellä on suorituskykykustannus. Kun vahva viitelaskuri nollautuu, hallittu olio tuhotaan; ohjauslohko säilyy, kunnes myös heikot viittaukset on poistettu. `weak_ptr` osoittaa samaan ohjauslohkoon pidentämättä olion elinkaarta; `lock()` palauttaa `shared_ptr`-arvon, jos olio on edelleen elossa, ja muussa tapauksessa tyhjän `shared_ptr`-arvon. Pelkistä `shared_ptr`-olioista muodostuvat syklit aiheuttavat muistivuodon, koska vahvat viitelaskurit eivät koskaan saavuta nollaa. Siksi `weak_ptr`-osoittimia käytetään paluuviittauksiin tai tarkkailijalinkkeihin. `enable_shared_from_this` antaa olion, jonka `shared_ptr` jo omistaa, luoda uuden `shared_ptr`-osoittimen itseensä olemassa olevaa ohjauslohkoa käyttäen, mikä välttää vaaralliset erilliset ohjauslohkot. Viitelaskurin kustannuksiin kuuluvat atomiset lisäykset ja vähennykset, välimuistikilpailu (cache contention), ohjauslohkon muistinvaraus ja ylimääräinen kuorma suorituskyvyn kannalta kriittisissä koodin osissa.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Tyyppijärjestelmä

5Mitä tarkoittaa siirtosemantiikka (move semantics), ja kuinka toteutat oikeaoppisen siirtokonstruktorin ja siirtosijoitusoperaattorin?

Siirtosemantiikka sallii C++:n siirtää resursseja väliaikaisista tai muutoin tarpeettomista olioista niiden kopioimisen sijaan. Se hyödyntää rvalue-viittauksia, kuten `T&&`, sekä `std::move`-funktiota, joka on tyyppimuunnos, joka mahdollistaa siirtoylikuormitusten valinnan; `std::move` itsessään ei siirrä mitään. Oikeaoppinen siirtokonstruktori alustaa uuden olion ottamalla lähtöolion resurssin ja jättämällä lähtöolion kelvolliseen, purettavissa (destructible) ja sijoitettavissa olevaan tilaan. Oikeaoppinen siirtosijoitusoperaattori siirtää resurssin olemassa olevaan olioon, käsittelee tai sietää itsesijoituksen, vapauttaa tai käyttää uudelleen kohteen nykyisen resurssin, ottaa haltuunsa lähteen resurssin ja jättää lähteen turvalliseen tilaan. Siirto-operaatioiden tulisi usein olla `noexcept`-määriteltyjä, jotta standardikirjaston säiliöt voivat käyttää niitä uudelleenvarauksen aikana säilyttäen poikkeustakuut.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

6Kuvaile const-korrektiutta (const-correctness) C++:n rajapinnoissa ja miten suunnitella const-jäsenfunktioita tehokkaasti.

Const-korrektius tarkoittaa sen ilmaisemista tyyppijärjestelmän kautta, mitkä operaatiot eivät muokkaa olion havaittavaa tai loogista tilaa. `const`-jäsenfunktiolla on `const`-määreellä varustettu `this`-olio, joten se ei voi muokata jäsenmuuttujia, joilla ei ole `mutable`-määrettä, eikä kutsua saman olion ei-`const`-jäsenfunktioita. Hyvässä rajapintasuunnittelussa vain luku -kyselyt merkitään `const`-määreellä, palautetaan arvoja tai `const`-viittauksia/-osoittimia soveltuvin osin, ja vältetään sisäisen, muuttuvan tilan paljastamista `const`-funktioista. `mutable`-avainsana tulisi varata toteutusyksityiskohdille, jotka eivät muuta loogista tilaa, kuten välimuisteille, laiskasti laskettaville arvoille (lazy values), metriikalle tai mutexeille. `const` on rajapintasopimus muuttumattomuudesta, ei automaattinen tae säieturvallisuudesta; samanaikaisuustakuut (concurrency guarantees) vaativat erillisen toteutuksen ja dokumentaation.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

7Mitä ovat vain siirrettävät (move-only) tyypit, ja miten ne vaikuttavat API-rajapintojen rajoihin verkkosocketien, tiedostokahvojen ja lukkojen osalta?

Vain siirrettävät tyypit ovat tyyppejä, joita ei voi kopioida, mutta joita voi siirtää. Ne ovat yleisiä resurssien, kuten verkkosocketien, tiedostokuvaajien, tiedostokahvojen, lukkojen ja `std::unique_ptr`-osoittimien yksinomaisessa omistajuudessa. Kopiointioperaatiot on poistettu (deleted), jotta vältetään kahdennettu omistajuus ja resurssin vapauttaminen kahteen kertaan; siirto-operaatiot siirtävät omistajuuden ja jättävät lähteen kelvolliseen tilaan, mutta yleensä tyhjäksi tai ilman omistajuutta. API-rajapintojen tulisi tehdä omistajuusrajat selviksi: tehdasfunktiot voivat palauttaa vain siirrettäviä olioita arvona, omistajuuden ottavat funktiot voivat vastaanottaa niitä arvona tai rvalue-viitteenä, ja funktioiden, jotka vain tarkastelevat niitä, tulisi ottaa viitteitä, osoittimia tai muita lainausmekanismeja. Säiliöt voivat tallentaa vain siirrettäviä arvoja, kun alkioita lisätään tai siirretään siirto-operaatiolla.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

8Vertaile `auto`-, `decltype`- ja `decltype(auto)`-avainsanoja sekä mallien argumenttien päättelyä (template argument deduction) tyypillisessä taustajärjestelmäkoodissa.

`auto` käyttää mallien kaltaista päättelyä muuttujille: pelkkä `auto` jättää yleensä pois viittaukset ja ylimmän tason `const`-määreet, ellei esittelyssä erikseen pyydetä niitä (esim. `auto&`, `const auto&` tai `auto&&`). `decltype(expr)` tutkii esitellyn tyypin tai lausekkeen tyypin tarkemmin: sulkeeton id-lauseke antaa esitellyn tyypin, kun taas muut `lvalue`-lausekkeet tuottavat tyypin `T&`, `xvalue`-lausekkeet tyypin `T&&` ja `prvalue`-lausekkeet tyypin `T`. `decltype(auto)` päättelee tyypin käyttäen `decltype`-sääntöjä, ja sitä käytetään usein palautustyypeissä silloin, kun viittaukset on säilytettävä. Mallien argumenttien päättely on samankaltainen kuin `auto`, mutta se riippuu parametrin muodosta (kuten `T`, `T&`, `const T&` tai `T&&`) ja sillä on omat sääntönsä. Aaltosulkeilla tehtävät alustukset ovat yleinen ero: `auto x = {1,2}` päättelee tyypiksi `std::initializer_list<int>`, kun taas tavallinen malliparametri ei yleensä pysty päättelemään tyyppiä `T` pelkästä aaltosulkualustuksesta, ellei parametri odota `initializer_list`-tyyppiä tai muuta sopivaa tyyppiä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Olioiden elinkaari

9Kuvaile nollan sääntö (Rule of Zero), kolmen sääntö (Rule of Three) ja viiden sääntö (Rule of Five) sekä milloin kutakin sovelletaan.

Nollan sääntö: suosi luokkia, jotka eivät määrittele omia destruktoreita, kopio- tai siirto-operaatioita; anna RAII (Resource Acquisition Is Initialization) -jäsenten, kuten `std::string`, `std::vector`, `std::unique_ptr` sekä tiedostojen tai verkkosocketien kääreiden, hallita resursseja. Kolmen sääntö: jos luokka hallitsee resurssia manuaalisesti ja tarvitsee kustomoidun destruktorin, kopiokonstruktorin tai kopiosijoitusoperaattorin, se yleensä tarvitsee kaikki kolme määrittääkseen oikeanlaisen kopiointi- ja omistajuuskäyttäytymisen. Viiden sääntö: C++11:ssä ja uudemmissa tällaisissa tyypeissä tulisi harkita myös siirtokonstruktoria ja siirtosijoitusoperaattoria. Käytä nollan sääntöä useimmille sovellustason tyypeille; käytä kolmen tai viiden sääntöä silloin, kun tyyppi omistaa resurssin suoraan tai kun sillä on epätriviaalia omistajuus- tai elinkaarisemantiikkaa.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Kielen semantiikka

10Kuvaile C++:n alustusmuodot ja yleiset sudenkuopat, mukaan lukien `initializer_list` ja aggregaatit.

C++:ssa on useita alustusmuotoja. Oletusalustus (default initialization), kuten `T x;`, kutsuu luokkatyyppien oletuskonstruktoria, mutta jättää perusteistyypin automaattiset muuttujat alustamatta. Arvoalustus (value initialization), kuten `T x{};` tai `T()`, nolla-alustaa muuttujat soveltuvin osin ennen konstruktorin tai jäsenten alustusta. Listaalustus (list initialization) käyttää aaltosulkeita, hylkää kaventavat tyyppimuunnokset (narrowing conversions) ja noudattaa erityisiä kuormituksenratkaisusääntöjä (overload resolution), mukaan lukien vahva etusija kelvollisille `std::initializer_list`-konstruktoreille. Aggregaattialustus (aggregate initialization) alustaa aggregaattien jäsenet suoraan aaltosulkeilla; C++20 tukee myös aggregaattien nimettyjä alustajia (designated initializers) esittelyjärjestyksessä. Yleisiä sudenkuoppia ovat alustamattomat lokaalit skalaarimuuttujat, yllättävä `initializer_list`-kuormituksen valinta, kavennusvirheet aaltosulkeita käytettäessä, C++:n jäsennysongelma "most vexing parse" sulkeita käytettäessä ja toiminnan muuttuminen silloin, kun tyyppi lakkaa olemasta aggregaatti.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

11Mitä on määrittelemätön toiminta (undefined behavior) C++-kielessä, ja miten se voi ilmetä taustajärjestelmien (backend) tuotantoympäristön häiriötilanteissa?

Määrittelemätön toiminta on toimintaa, jolle C++-standardi ei aseta mitään vaatimuksia virheellisen operaation tapahduttua. Ohjelma saattaa vaikuttaa toimivan normaalisti, kaatua, korruptoida dataa, altistaa tietoturva-aukoille tai tulla optimoiduksi tuottamaan yllättävää toimintaa. Kääntäjät olettavat, ettei määrittelemätöntä toimintaa (UB) tapahdu, ja tekevät optimointeja tähän oletukseen perustuen, joten ongelmat saattavat ilmetä vasta julkaisuversioissa (release build) tai tuotantoliikenteen alla. Taustajärjestelmien häiriöt voivat johtua roikkuvista osoittimista tai viittauksista (dangling pointers/references), vapautetun muistin käytöstä (use-after-free), olioiden elinkaarisääntöjen rikkomuksista, rajojen ulkopuolisesta muistipääsystä, etumerkillisten kokonaislukujen ylivuodosta, datakilpailuista (data races), virheellisistä tyyppimuunnoksista, alustamattoman muistin lukemisesta, kahdesti vapauttamisesta (double free) tai strict aliasing -sääntöjen rikkomuksista. Torjuntakeinoihin kuuluvat RAII (Resource Acquisition Is Initialization) ja selkeä omistajuuden sekä elinkaaren suunnittelu, turvallisemmat abstraktiot ja rajatarkistukset, testaus ja sumeustestaus (fuzzing), koodikatselmoinnit, staattinen analyysi sekä sanitizer-työkalut, kuten ASan, UBSan ja TSan.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Object Model

12Selitä virtuaalifunktiot, vtable-taulut, dynaamisen sidonnan (dynamic dispatch) kustannukset, olioiden leikkautuminen (object slicing) ja virtuaalisiin destruktoreihin liittyvät riskit.

Virtuaalifunktio mahdollistaa ajonaikaisen polymorfismin: kun funktiota kutsutaan kantaluokan osoittimen tai viitteen kautta, suoritettava toteutus valitaan olion dynaamisen tyypin perusteella. Useimmat toteutukset tallentavat jokaiseen polymorfiseen olioon piilotetun vptr-osoittimen, joka osoittaa kyseisen dynaamisen tyypin virtuaalifunktioiden osoitteita sisältävään vtable-tauluun. Tyypillisiä dynaamisen sidonnan kustannuksia ovat ylimääräinen osoitin oliossa, epäsuora funktiokutsu, mahdolliset vaikutukset välimuistiin ja haarautumisen ennakointiin (branch prediction) sekä vähentyneet mahdollisuudet koodin sisällyttämiseen (inlining), vaikkakin kääntäjät voivat joskus devirtualisoida näitä kutsuja. Olion leikkautuminen (object slicing) tapahtuu, kun aliluokan olio kopioidaan tai tallennetaan arvon (by value) kautta kantaluokan oliona, jolloin se menettää aliluokkakohtaisen datan ja dynaamisen käyttäytymisen. Jos kantaluokka on tarkoitettu poistettavaksi kantaluokan osoittimen kautta, sen destruktorin on oltava virtuaalinen; muussa tapauksessa aliluokan olion poistaminen kyseisen kantaluokan osoittimen kautta johtaa määrittelemättömään toimintaan (undefined behavior).

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Standardikirjasto

13Kuvaile `std::chrono`-kirjaston kelloja, aikapisteitä ja kestoja palvelujen aikakatkaisujen, metriikoiden ja aikaleimojen hallinnassa.

`std::chrono` mallintaa aikaa kellojen (clocks), aikapisteiden (`time_point`) ja kestojen (`duration`) avulla. Kesto (`duration`) on aikaväli ja sen yksikkö, kuten millisekunti tai sekunti. Aikapiste (`time_point`) on piste tietyn kellon aikajanalla. `steady_clock` on monotoninen, ja sitä tulisi käyttää kuluneen ajan, palvelujen aikakatkaisujen, määräaikojen ja viiveiden mittaamiseen, koska seinäkellon muutokset eivät vaikuta siihen. `system_clock` edustaa siviili- eli seinäkellonaikaa, ja se sopii aikaleimoihin, lokitukseen, tietojen pysyväistallennukseen ja kalenterimuunnoksiin, mutta sen aika voi hypätä järjestelmän aikaa säädettäessä. Aikakatkaisujen tulisi yleensä perustua kaavaan `steady_clock::now() + duration`; koneellisten aikaleimojen tulisi käyttää selkeästi dokumentoitua seinäkellon muotoa, tyypillisesti UTC-aikaa tallennuksen ja API:n (Application Programming Interface) rajoilla.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

14Kuvaile std::format ja modernit muotoiluominaisuudet verrattuna iostreams- ja printf-tyylisiin API-rajapintoihin.

`std::format` on C++20:n tyyppiturvallinen muotoiluominaisuus, joka on saanut inspiraationsa `fmtlib`-kirjastosta. Se käyttää `{}`-korvauskenttiä ja muotoilumäärityksiä muotoillun tekstin tuottamiseen ilman iostreamsin tilallista lisäyssyntaksia tai `printf`-tyylisiä C:n vaihtuvamittaisia `varargs`-argumentteja. Verrattuna `printf`-funktioon se välttää monia muotoilun ja tyypin yhteensopimattomuusongelmia; verrattuna iostreams-kirjastoon se on usein selkeämpi ja helpompi koostaa. `fmtlib` on laajasti käytetty kirjasto, joka edelsi ja vaikutti `std::format`-kirjastoon, ja se voi tarjota laajemman tuen tai uudempia ominaisuuksia. Käyttäjän määrittelemät tyypit voidaan muotoilla mukautettujen muotoilijoiden avulla. Suuren volyymin lokituksessa suorituskyky riippuu tarpeettomien muotoilujen, muunnosten ja muistinvarausten välttämisestä, erityisesti pois käytöstä otetuilla lokitasoilla; API-rajapinnat, jotka lykkäävät muotoilua tai tarkistavat lokitason ensin, ovat suositeltavia.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Mallit

15Selitä C++:n arvokategoriat (value categories) ja täydellinen välittäminen (perfect forwarding) sekä se, miksi ne ovat tärkeitä tehokkaissa ja geneerisissä API-rajapinnoissa.

C++:n arvokategoriat kuvaavat lausekkeita: lvalue-arvoilla on identiteetti ja niihin voidaan viitata lausekkeen jälkeen; prvalue-arvot ovat puhtaita rvalue-arvoja, kuten monet väliaikaiset tai lasketut arvot; xvalue-arvot ovat elinkaarensa päässä olevia olioita, joiden resurssit voidaan käyttää uudelleen. Täydellinen välittäminen on mallipohjainen tekniikka, jossa käytetään välittävää viittausta (forwarding reference), yleensä `T&&`, jossa `T` on päätelty tyyppi, ja argumentti välitetään `std::forward<T>(arg)` -funktiolla. Näin kutsujan arvokategoria säilyy: lvalue-arvot pysyvät lvalue-arvoina ja rvalue-arvot rvalue-arvoina. Viittausten yhdistämissäännöt (reference collapsing) tekevät tämän mahdolliseksi. Tämä on tärkeää taustajärjestelmien geneerisissä API-rajapinnoissa, koska kääreluokat, tehtaat, sanomanvälittäjät (dispatchers) ja `emplace`-tyyppiset funktiot voivat välttää tarpeettomat kopiot ja säilyttää ylikuormituksen valinnan (overload selection) sekä siirtosemantiikan toiminnan.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Rinnakkaisuus

16Kuvaile lukkiutumisen (deadlock) estämistä ja usean muteksin lukitusstrategioita käyttämällä `std::scoped_lock` ja `std::lock` -rakenteita.

Lukkiutuminen estetään välttämällä kehäodottamista (circular wait): hanki lukot mahdollisuuksien mukaan johdonmukaisessa globaalissa järjestyksessä tai lukitse useampi muteksi `std::lock`- tai `std::scoped_lock`-rakenteilla, jotka käyttävät lukkiutumisen välttämisalgoritmia. `std::scoped_lock lock(a, b, ...)` on yksinkertaisin RAII (Resource Acquisition Is Initialization) -muoto usean muteksin lukitsemiseen ja niiden automaattiseen vapauttamiseen näkyvyysalueelta poistuttaessa. Kun käytät `std::lock`-funktiota, lukitse ensin muteksit ja liitä niihin sitten RAII-kääreet käyttämällä `std::adopt_lock`-parametria, tai käytä `std::unique_lock`-käärettä `std::defer_lock`-parametrin kanssa. Pidä kriittiset alueet lyhyinä ja vältä estäviä operaatioita tai tuntemattomia takaisinkutsuja lukkojen ollessa varattuina. Lukkiutumisen välttäminen ei automaattisesti takaa reiluutta eikä estä kaikkia elävän lukkiutumisen (livelock) tai resurssipulan (starvation) skenaarioita.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

17Selitä `condition_variable`-luokan käyttötavat tilamuutosten odottamiseen.

Käytä `std::condition_variable`-luokkaa odottamaan mutex-lukolla suojatun tilapredikaatin muuttumista. Jaettua tilaa muokataan pitämällä samaa mutex-lukkoa varattuna, minkä jälkeen `notify_one` tai `notify_all` herättää odottajat. Odottajien tulisi käyttää `cv.wait(lock, predicate)` -kutsua tai vastaavaa silmukkaa, koska herätykset voivat olla aiheettomia (spurious wakeup) eikä ilmoituksia muisteta predikaatin tilasta riippumatta. `notify_one` herättää yhden odottajan; `notify_all` herättää kaikki odottajat ja sopii laajempiin tilamuutoksiin, kuten järjestelmän alasajoon.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

18Suunnittele säieturvallinen rajoitetun kapasiteetin jono käyttäen C++:n standardikirjaston primitiivejä ja määrittele sen API-takuut.

Rajoitettu säieturvallinen jono voidaan rakentaa käyttämällä `std::mutex`-oliota, ehtomuuttujia, kiinteän kapasiteetin säiliötä ja sulkemisesta kertovaa lippua. `push`-operaatio joko estää suorituksen, aikakatkaistaan tai epäonnistuu jonon ollessa täynnä, mikä luo vastapainetta; `pop`-operaatio estää suorituksen, kun jono on tyhjä. Molempien operaatioiden tulisi odottaa ehtoja, kuten `size < capacity || closed` ja `!empty || closed`. Kun jono suljetaan, herätä estetyt tuottajat ja kuluttajat; hylkää uudet `push`-kutsut ja määrittele, tyhjentävätkö kuluttajat olemassa olevat alkiot vai pysähtyvätkö ne välittömästi. API-rajapinnan tulisi määritellä estämiskäyttäytyminen, sulkemisen semantiikka, paluuarvot sekä rinnakkaisuustakuut.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Database

19Miten toteutat tietokantaskeeman migraation C++-palvelulle ilman käyttökatkoa?

Käytä vaiheittaista expand-contract -migraatiota. Tee ensin taaksepäin yhteensopivia lisäyksiä skeemaan, kuten NULL-arvoja sallivia sarakkeita tai uusia tauluja, jotka eivät riko käynnissä olevaa C++-palvelua. Julkaise yhteensopivaa koodia, joka pystyy käsittelemään sekä vanhaa että uutta esitystapaa, täydennä olemassa oleva data pienissä rajoitetuissa erissä, validoi datan yhtenäisyys, siirrä lukuoperaatiot uuteen skeemaan, ja poista vanhat sarakkeet tai koodi vasta myöhemmin, kun mitkään tuotannossa olevat versiot eivät enää riipu niistä. Vältä suurilla tauluilla pitkiä ja lukitsevia DDL (Data Definition Language) -operaatioita, käytä online- tai samanaikaisia operaatioita silloin kun niitä tuetaan, valvo lukkoja, replikointia ja resursseja, ja varmista peruutusreitit, jotka toimivat osittain julkaistun koodin ja osittain migroidun datan kanssa.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

20Miten C++-palvelun tulisi suorittaa lukuoperaatioita tietokantareplikoista siten, että hallitaan vanhentunutta dataa (stale reads) sekä taataan omien kirjoitusten lukeminen (read-your-writes)?

Käsittele replikalukuja tietoisena kompromissina tietojen yhdenmukaisuuden suhteen. Lukuoperaatiot, jotka sietävät vanhentunutta dataa, voidaan ohjata terveisiin replikoihin. Omien kirjoitusten lukemista vaativat, transaktionaaliset tai tuoreuden kannalta kriittiset lukuoperaatiot tulisi kuitenkin ohjata pääkantaan, ellei valitun replikan tiedetä suorittaneen asianmukaista kirjoituskohtaa, kuten LSN (Log Sequence Number) -numeroa, aikaleimaa tai versiota. Seuraa replikoiden viivettä ja tilaa, sisällytä vaadittu yhdenmukaisuustaso päätepiste- tai pyyntökohtaisesti ja varaudu käyttämään pääkantaa, odottamaan replikan päivittymistä tai palauttamaan virhe, jos replikat ylittävät sallitun vanhentuneisuuden. Dokumentoi yhdenmukaisuusmalli, jotta kutsujat tietävät, mitkä lukuoperaatiot saattavat palauttaa vanhentunutta dataa.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa