1Objasnite RAII (Resource Acquisition Is Initialization) i kako on utiče na upravljanje resursima bezbedno u odnosu na izuzetke u C++ backend servisima.
RAII (Resource Acquisition Is Initialization) označava koncept u kome C++ objekat poseduje resurs i oslobađa ga u svom destruktoru. Pošto se lokalni objekti automatski uništavaju kada se njihov životni vek ili opseg vidljivosti završi, uključujući i tokom odmotavanja steka pri izuzecima (stack unwinding), RAII omogućava determinističko čišćenje resursa i čini grane za obradu grešaka bezbednim u odnosu na izuzetke. U backend servisima se ovo ne odnosi samo na memoriju već i na deskriptore fajlova, sokete, zaključavanja muteksa (mutex locks), ručke baza podataka (database handles), transakcije i druge resurse operativnog sistema ili aplikacije.
class Fd {
public:
explicit Fd(int fd = -1) noexcept : fd_(fd) {}
~Fd() { if (fd_ != -1) ::close(fd_); }
Fd(const Fd&) = delete;
Fd& operator=(const Fd&) = delete;
Fd(Fd&& other) noexcept : fd_(std::exchange(other.fd_, -1)) {}
Fd& operator=(Fd&& other) noexcept {
if (this != &other) {
if (fd_ != -1) ::close(fd_);
fd_ = std::exchange(other.fd_, -1);
}
return *this;
}
int get() const noexcept { return fd_; }
private:
int fd_;
};
void handle_request() {
Fd sock(::accept(...));
if (sock.get() == -1) throw std::runtime_error("accept failed");
// If any later operation throws, sock is still closed by ~Fd().
}
2Uporedite std::unique_ptr i std::shared_ptr i opišite kada je svaki od njih prikladan u backend API (Application Programming Interface) rešenjima.
`std::unique_ptr` predstavlja ekskluzivno vlasništvo: ima minimalne troškove, može se premeštati ali ne i kopirati, i pogodan je za jednog vlasnika ili za API funkcije koje prenose vlasništvo. `std::shared_ptr` predstavlja deljeno vlasništvo: može se kopirati i održava objekat u životu pomoću brojanja referenci sve dok ga poslednji jaki vlasnik ne oslobodi. U backend API-jima, koristite `unique_ptr` kada se vlasništvo prenosi, `shared_ptr` samo kada više nezavisnih vlasnika mora produžiti životni vek objekta, a reference ili sirove pokazivače za pristup bez vlasništva. `std::make_shared` se obično preporučuje pri kreiranju `shared_ptr` objekata jer je efikasan i bezbedan u odnosu na izuzetke.
3Kako funkcionišu prilagođeni brisači (custom deleters) u pametnim pokazivačima i kada su korisni za interoperabilnost između C i C++ jezika?
Prilagođeni brisač je pozivna logika za oslobađanje resursa koju pametni pokazivač koristi umesto podrazumevane delete operacije kada otpušta resurs kojim upravlja. Koristan je za interoperabilnost između C i C++ jezika kada resurs mora biti oslobođen specifičnom funkcijom kao što su fclose, close, curl_easy_cleanup, SSL_free, free ili funkcija za uništavanje iz neke biblioteke. Kod unique_ptr, tip brisača je deo tipa unique_ptr i može uticati na njegovu veličinu; brisači bez stanja (stateless) mogu biti optimizovani tako da ne zauzimaju memoriju, dok pokazivači na funkcije ili brisači sa stanjem dodaju memorijsko zauzeće. Kod shared_ptr, brisač se čuva u kontrolnom bloku i izvršava se kada poslednji jaki vlasnik otpusti objekat. Prilagođeni brisači omogućavaju da C resursi bezbedno učestvuju u RAII (Resource Acquisition Is Initialization) obrascu.
4Objasnite mehaniku kontrolnog bloka za weak_ptr i shared_ptr, uključujući ciklične reference, enable_shared_from_this i troškove brojanja referenci.
`shared_ptr` upravlja deljenim vlasništvom putem kontrolnog bloka koji sadrži brojače jakih i slabih referenci, kao i informacije o brisanju resursa kao što su brisač (`deleter`) i alokator. Kopiranje ili uništavanje `shared_ptr` objekata uvećava ili umanjuje broj jakih referenci, obično pomoću atomičnih operacija, pa se zasebnim `shared_ptr` objektima može bezbedno manipulisati kroz više niti, ali svako ažuriranje brojača referenci nosi određeni trošak. Kada broj jakih referenci padne na nulu, upravljani objekat se uništava; kontrolni blok ostaje u memoriji sve dok ne nestanu i sve slabe reference. `weak_ptr` pokazuje na isti kontrolni blok bez produžavanja životnog veka objekta; `lock()` vraća `shared_ptr` ako je objekat još uvek živ, a prazan `shared_ptr` u suprotnom. Ciklične reference sačinjene isključivo od `shared_ptr` dovode do curenja memorije jer broj jakih referenci nikada ne dostiže nulu, pa se `weak_ptr` koristi za povratne pokazivače ili veze posmatrača (`observer`). `enable_shared_from_this` omogućava objektu koji već poseduje `shared_ptr` da kreira novi `shared_ptr` ka samom sebi koristeći postojeći kontrolni blok, čime se izbegava opasnost od stvaranja višestrukih zasebnih kontrolnih blokova. Troškovi brojanja referenci obuhvataju atomično uvećavanje/umanjivanje, konflikte u kešu (`cache contention`), alokaciju kontrolnog bloka i dodatno opterećenje u delovima koda kritičnim za performanse.
struct Parent;
struct Child {
std::weak_ptr<Parent> parent; // non-owning back-reference
};
struct Parent {
std::vector<std::shared_ptr<Child>> children; // owns children
};
void example() {
auto p = std::make_shared<Parent>();
auto c = std::make_shared<Child>();
c->parent = p;
p->children.push_back(c);
if (auto locked = c->parent.lock()) {
// safe use while Parent is still alive
}
}
5Šta je semantika premeštanja (move semantics) i kako se implementiraju ispravan konstruktor premeštanja i operator dodele premeštanjem?
Semantika premeštanja omogućava jeziku C++ da preuzme resurse iz privremenih ili na drugi način potrošnih objekata umesto da ih kopira. Ona koristi r-vrednosne reference kao što su T&& i std::move, što je kastovanje koje omogućava izbor preopterećenja za premeštanje; sam std::move ne premešta ništa. Ispravan konstruktor premeštanja inicijalizuje novi objekat preuzimanjem resursa izvornog objekta i ostavlja izvorni objekat u validnom stanju koje se može uništiti i kome se može dodeliti nova vrednost. Ispravan operator dodele premeštanjem vrši prenos u postojeći objekat, obrađuje ili toleriše samododelu, oslobađa ili ponovo koristi trenutni resurs odredišta, preuzima izvorni resurs i ostavlja izvor bezbednim. Operacije premeštanja bi često trebalo da budu noexcept kako bi standardni kontejneri mogli da ih koriste tokom realokacije uz očuvanje garancija pri izuzecima.
6Opišite const-korektnost (const-correctness) u C++ API-jima (Application Programming Interface) i kako efikasno dizajnirati const članske funkcije.
Const-korektnost podrazumeva izražavanje kroz sistem tipova koje operacije ne menjaju uočljivo ili logičko stanje objekta. `const` članska funkcija ima `const`-kvalifikovan `this` objekat, tako da ne može menjati članove podataka koji nisu označeni kao `mutable`, niti pozivati ne-`const` članske funkcije nad istim objektom. Dobar dizajn API-ja označava funkcije za čitanje kao `const`, vraća vrednosti ili `const` reference/pokazivače kada je to prikladno i izbegava izlaganje promenljivog unutrašnjeg stanja iz `const` funkcija. Ključna reč `mutable` treba da bude rezervisana za detalje implementacije koji ne menjaju logičko stanje, kao što su keševi, lenjo izračunate vrednosti, metrike ili muteksi. `const` je ugovorna obaveza na nivou API-ja o promenama stanja, a ne automatska garancija bezbednosti niti; garancije konkurentnosti zahtevaju zasebnu implementaciju i dokumentaciju.
7Šta su tipovi koji podržavaju samo pomeranje (move-only types) i kako oni utiču na granice API (Application Programming Interface) interfejsa za sokete, deskriptore fajlova i katance?
Tipovi koji podržavaju samo pomeranje jesu tipovi koji se ne mogu kopirati, već se mogu samo pomerati. Uobičajeni su za jedinstveno vlasništvo nad resursima kao što su soketi, deskriptori fajlova, rukovaoci fajlovima, katanci i `std::unique_ptr`. Operacije kopiranja su obrisane kako bi se izbeglo duplicirano vlasništvo i dvostruko oslobađanje; operacije pomeranja prenose vlasništvo i ostavljaju izvorni objekat u validnom, ali obično praznom stanju bez vlasništva. API interfejsi treba jasno da definišu granice vlasništva: fabričke funkcije mogu vraćati objekte koji se samo pomeraju po vrednosti, funkcije koje preuzimaju vlasništvo mogu ih prihvatati po vrednosti ili preko rvalue reference, a funkcije koje samo vrše uvid treba da primaju reference, pokazivače ili druge pozajmljene ručke. Kontejneri mogu skladištiti vrednosti koje se samo pomeraju kada se elementi umeću ili premeštaju pomeranjem.
#include <utility>
class Fd {
public:
explicit Fd(int fd = -1) noexcept : fd_(fd) {}
~Fd() { close_if_valid(); }
Fd(const Fd&) = delete;
Fd& operator=(const Fd&) = delete;
Fd(Fd&& other) noexcept : fd_(std::exchange(other.fd_, -1)) {}
Fd& operator=(Fd&& other) noexcept {
if (this != &other) {
close_if_valid();
fd_ = std::exchange(other.fd_, -1);
}
return *this;
}
int get() const noexcept { return fd_; }
explicit operator bool() const noexcept { return fd_ != -1; }
private:
void close_if_valid() noexcept {
if (fd_ != -1) {
// ::close(fd_); // real POSIX code would close here
fd_ = -1;
}
}
int fd_;
};
Fd open_socket(); // factory returns ownership by value
void take_socket(Fd fd); // takes ownership
void inspect_socket(const Fd&); // borrows only
void example() {
Fd s = open_socket();
inspect_socket(s);
take_socket(std::move(s));
}
8Uporedite auto, decltype, decltype(auto) i dedukciju argumenata šablona u uobičajenom backend kodu.
auto koristi dedukciju nalik na šablone za promenljive: obično auto uglavnom odbacuje reference i const na najvišem nivou (top-level const) osim ako deklaracija to ne zahteva, kao što su auto&, const auto& ili auto&&. decltype(expr) preciznije proverava deklarisani tip ili tip izraza: nezagrađeni id-izraz daje deklarisani tip, dok ostali lvalue izrazi daju T&, xvalue izrazi daju T&&, a prvalue izrazi daju T. decltype(auto) vrši dedukciju koristeći decltype pravila, često za povratne tipove kada reference moraju biti očuvane. Dedukcija argumenata šablona je slična kao kod auto, ali zavisi od oblika parametra, kao što su T, T&, const T& ili T&&, i ima svoja specifična pravila. Inicijalizatori u vitičastim zagradama predstavljaju čestu razliku: auto x = {1,2} dedukuje std::initializer_list<int>, dok običan parametar šablona generalno ne može dedukovati T iz samog inicijalizatora u vitičastim zagradama osim ako parametar ne očekuje initializer_list ili drugi odgovarajući tip.
int x = 1;
const int cx = 2;
int& rx = x;
auto a = rx; // int, copy of x
auto b = cx; // int, top-level const dropped
const auto& c = cx; // const int&, reference preserved by declaration
a = 10; // does not change x
9Opišite Pravilo nule (Rule of Zero), Pravilo trojke (Rule of Three) i Pravilo petice (Rule of Five) i kada se koje primenjuje.
Pravilo nule: dajte prednost klasama koje ne deklarišu prilagođeni destruktor, operacije kopiranja ili premeštanja; prepustite članovima koji koriste RAII (Resource Acquisition Is Initialization), kao što su `std::string`, `std::vector`, `std::unique_ptr`, omotači za datoteke/sokete itd., da upravljaju resursima. Pravilo trojke: ako klasa ručno upravlja resursom i zahteva prilagođeni destruktor, konstruktor kopiranja ili operator dodele kopiranjem, obično su joj potrebna sva tri kako bi se definisalo ispravno ponašanje kopiranja i prenosa vlasništva. Pravilo petice: u standardu C++11 i novijim, takvi tipovi bi takođe trebalo da razmotre konstruktor premeštanja i operator dodele premeštanjem. Koristite Pravilo nule za većinu aplikativnih tipova; koristite Pravilo trojke/petice kada tip direktno poseduje resurs ili ima netrivijalnu semantiku vlasništva/životnog veka.
10Opišite oblike inicijalizacije u jeziku C++ i uobičajene zamke, uključujući `initializer_list` i agregate.
C++ ima nekoliko oblika inicijalizacije. Podrazumevana inicijalizacija, kao što je `T x;`, poziva podrazumevani konstruktor za klasne tipove, ali ostavlja automatske osnovne promenljive neinicijalizovanim. Inicijalizacija vrednošću, kao što su `T x{};` ili `T()`, postavlja vrednosti na nulu gde je primenljivo pre inicijalizacije konstruktora/članova. Inicijalizacija listom koristi vitičaste zagrade, odbacuje konverzije sa sužavanjem opsega i ima posebna pravila za razrešavanje preklapanja, uključujući izrazitu prednost konstruktorima koji prihvataju `std::initializer_list`. Agregatna inicijalizacija inicijalizuje članove agregata direktno pomoću vitičastih zagrada; C++20 takođe podržava imenovane inicijalizatore za agregate, po redosledu deklaracije. Uobičajene zamke obuhvataju neinicijalizovane lokalne skalare, neočekivan izbor preklopljenog konstruktora sa `initializer_list`, greške usled sužavanja tipa kod vitičastih zagrada, problem najdvosmislenijeg parsiranja sa običnim zagradama i promene u ponašanju kada tip prestane da bude agregat.
#include <iostream>
struct S {
int x;
};
int main() {
int a; // default-initialized: indeterminate value
int b{}; // value/list-initialized: zero
S s{}; // aggregate/value initialization: s.x is zero
std::cout << b << ' ' << s.x << '\n';
(void)a; // reading a would be undefined behavior
}
11Šta je nedefinisano ponašanje u jeziku C++ i kako se može manifestovati u produkcionim incidentima na backend-u?
Nedefinisano ponašanje jeste ponašanje za koje C++ standard ne postavlja nikakve zahteve nakon što dođe do nevalidne operacije. Program može izgledati kao da radi, može se srušiti, oštetiti podatke, stvoriti bezbednosne propuste ili se usled optimizacija ponašati neočekivano. Kompajleri pretpostavljaju da se nedefinisano ponašanje ne dešava i vrše optimizacije na osnovu te pretpostavke, pa se problemi često manifestuju tek u release verzijama ili pod produkcionim opterećenjem. Incidenti na backend-u mogu nastati usled visećih pokazivača/referenci, korišćenja memorije nakon oslobađanja (use-after-free), narušavanja životnog veka objekata, pristupa van granica niza, prekoračenja označenih celih brojeva, trka za podacima, nevalidnih kastovanja, čitanja neinicijalizovane memorije, dvostrukog oslobađanja memorije ili kršenja pravila strogog aliasovanja (strict aliasing). Ublažavanje ovih problema uključuje primenu RAII (Resource Acquisition Is Initialization) idioma, jasno definisano vlasništvo i životni vek, bezbednije apstrakcije i proveru granica, testiranje/fuzzing, pregled koda, statičku analizu i sanitizere kao što su ASan, UBSan i TSan.
int grow(int x) {
if (x + 1 < x) {
return 0; // compiler may assume this is unreachable
}
return x + 1;
}
12Objasnite virtuelne funkcije, tabele virtuelnih funkcija (vtable), cenu dinamičkog otpremanja, odsecanje objekata (object slicing) i opasnosti virtuelnih destruktora.
Virtuelna funkcija omogućava polimorfizam u vreme izvršavanja: kada se pozove preko pokazivača ili reference na osnovnu klasu, bira se implementacija za dinamički tip tog objekta. Većina implementacija čuva skriveni pokazivač `vptr` u svakom polimorfnom objektu, koji pokazuje na tabelu virtuelnih funkcija (`vtable`) sa adresama virtuelnih funkcija za taj dinamički tip. Uobičajena cena obuhvata dodatni pokazivač u objektu, indirektni poziv, potencijalni uticaj na keš memoriju i predviđanje grananja, kao i smanjene mogućnosti za ugrađivanje koda (engl. inlining), mada kompajleri ponekad mogu izvršiti devirtuelizaciju. Odsecanje objekta (engl. object slicing) nastaje kada se izvedeni objekat kopira ili sačuva po vrednosti kao objekat osnovne klase, pri čemu se gubi izvedeni deo i dinamičko ponašanje. Ako je predviđeno da se objekat izvedene klase briše preko pokazivača na osnovnu klasu, destruktor osnovne klase mora biti virtuelan; u suprotnom, brisanje izvedenog objekta preko takvog pokazivača dovodi do nedefinisanog ponašanja.
13Opišite časovnike, vremenske tačke i trajanja iz biblioteke `std::chrono` za vremenska ograničenja servisa, metrike i vremenske oznake.
`std::chrono` modeluje vreme pomoću časovnika, `time_point` objekata i `duration` intervala. `duration` predstavlja interval sa jedinicom mere, kao što su milisekunde ili sekunde. `time_point` predstavlja tačku na vremenskoj liniji određenog časovnika. `steady_clock` je monoton i treba ga koristiti za proteklo vreme, vremenska ograničenja servisa, rokove i merenja kašnjenja zato što na njega ne utiču promene sistemskog časovnika. `system_clock` predstavlja realno/sistemsko vreme i pogodan je za vremenske oznake, beleženje u logove, perzistenciju i konverziju kalendara, ali može imati skokove kada se sistemsko vreme prilagodi. Vremenska ograničenja bi obično trebalo da se zasnivaju na `steady_clock::now() + duration`; mašinske vremenske oznake treba da koriste jasno dokumentovan format realnog vremena, obično UTC (Coordinated Universal Time) na granicama API-ja (Application Programming Interface) i skladišta podataka.
using namespace std::chrono_literals;
auto deadline = std::chrono::steady_clock::now() + 500ms;
while (!done()) {
if (std::chrono::steady_clock::now() >= deadline) {
throw std::runtime_error("timeout");
}
poll_once();
}
14Opišite `std::format` i moderne mogućnosti formatiranja u poređenju sa iostreams i API (Application Programming Interface) interfejsima u stilu funkcije printf.
`std::format` je tipski bezbedan mehanizam za formatiranje uveden u C++20, inspirisan bibliotekom fmtlib. Koristi `{}` polja za zamenu i specifikacije formata za generisanje formatiranog teksta bez sintakse umetanja sa stanjem iz iostreams-a ili promenljivog broja argumenata u C `printf` stilu. U poređenju sa `printf`, izbegava mnoge probleme nepoklapanja formata i tipova; u poređenju sa iostreams-om, često je jasniji i lakši za sklapanje. fmtlib je široko korišćena biblioteka koja je prethodila i uticala na `std::format`, a može pružiti širu podršku ili novije mogućnosti. Korisnički definisani tipovi se mogu formatirati uz podršku za prilagođeni formater. Kod evidentiranja velikog obima, performanse zavise od izbegavanja nepotrebnog formatiranja, konverzija i alokacija, posebno za isključene nivoe logovanja; poželjniji su API-ji koji odlažu formatiranje ili prvo proveravaju nivo logovanja.
15Objasnite kategorije vrednosti u jeziku C++ i savršeno prosleđivanje (perfect forwarding), kao i zašto su oni važni za efikasne generičke API-je (Application Programming Interface).
Kategorije vrednosti u jeziku C++ opisuju izraze: l-vrednosti (lvalues) imaju identitet i na njih se može referencirati nakon izraza; pr-vrednosti (prvalues) su čiste r-vrednosti poput mnogih privremenih/izračunatih vrednosti; x-vrednosti (xvalues) su objekti kojima ističe životni vek i čiji se resursi mogu ponovo iskoristiti. Savršeno prosleđivanje je šablonska tehnika uzimanja prosleđujuće reference, tipično T&& gde se T dedukuje, i prosleđivanja pomoću std::forward<T>(arg) tako da se očuva kategorija vrednosti pozivaoca: l-vrednosti ostaju l-vrednosti, a r-vrednosti ostaju r-vrednosti. Pravila kolapsiranja referenci (reference collapsing) ovo čine mogućim. Ovo je važno za generičke backend API-je jer omotači, fabrike, dispečeri i funkcije u stilu emplace mogu da izbegnu nepotrebna kopiranja i očuvaju izbor preopterećenja i ponašanje premeštanja.
16Opišite sprečavanje mrtvog čvora (deadlock) i strategije zaključavanja više muteksa korišćenjem `std::scoped_lock` i `std::lock`.
Mrtvi čvor (deadlock) se sprečava izbegavanjem kružnog čekanja: zaključavanja treba pribavljati uvek istim globalnim redosledom kada je to moguće, ili zaključati više muteksa odjednom pomoću `std::lock`/`std::scoped_lock`, koji koriste algoritam za izbegavanje mrtvog čvora. `std::scoped_lock lock(a, b, ...)` predstavlja najjednostavniji RAII (Resource Acquisition Is Initialization) pristup za zaključavanje više muteksa i njihovo automatsko otključavanje po izlasku iz opsega vidljivosti. Sa `std::lock`, prvo se zaključavaju muteksi, a zatim se povezuju sa RAII omotačima pomoću `std::adopt_lock`, ili se koristi `std::unique_lock` uz `std::defer_lock`. Kritične sekcije treba da budu što kraće, a tokom držanja katanaca treba izbegavati blokirajuće operacije i nepoznate povratne pozive; izbegavanje mrtvog čvora samo po sebi ne garantuje pravednost niti u potpunosti eliminiše živi čvor (livelock) ili izgladnjivanje (starvation).
struct Account {
std::mutex m;
int balance{};
};
void transfer(Account& from, Account& to, int amount) {
if (&from == &to) return;
std::scoped_lock lock(from.m, to.m);
if (from.balance >= amount) {
from.balance -= amount;
to.balance += amount;
}
}
17Objasnite obrasce korišćenja klase condition_variable za čekanje promena stanja.
Koristite `std::condition_variable` za čekanje da se promeni predikat stanja zaštićenog muteksom. Deljeno stanje se menja dok se drži isti muteks, a zatim `notify_one` ili `notify_all` budi niti koje čekaju. Niti koje čekaju treba da koriste `cv.wait(lock, predicate)` ili ekvivalentnu petlju jer buđenja mogu biti lažna (spurious wakeups) i obaveštenja se ne pamte nezavisno od stanja predikata. `notify_one` budi jednu nit koja čeka; `notify_all` budi sve niti koje čekaju i pogodan je za obaveštavanje o promenama stanja koje utiču na sve, kao što je gašenje sistema.
18Dizajnirajte nitno bezbedan ograničeni red (thread-safe bounded queue) koristeći standardne C++ primitive i definišite garancije njegovog API-ja (Application Programming Interface).
Ograničeni nitno bezbedan red može se napraviti pomoću `std::mutex`, uslovnih promenljivih (condition variables), kontejnera fiksiranog kapaciteta i zastavice za gašenje/zatvaranje. Operacija `push` blokira, ističe nakon zadatog vremena ili ne uspeva kada je red pun, što obezbeđuje kontrolu pritiska (backpressure); `pop` blokira kada je red prazan. Obe operacije treba da čekaju na predikatima kao što su `size < capacity || closed` i `!empty || closed`. Prilikom gašenja/zatvaranja, treba probuditi blokirane proizvođače i potrošače; nove `push` pozive treba odbiti i definisati da li potrošači prazne preostale elemente ili se odmah zaustavljaju. API treba jasno da specificira ponašanje pri blokiranju, semantiku zatvaranja, povratne vrednosti i garancije konkurentnosti.
template<class T>
class BoundedQueue {
public:
explicit BoundedQueue(std::size_t cap) : cap_(cap) {}
// Returns false if the queue has been closed before the item is accepted.
bool push(T value) {
std::unique_lock<std::mutex> lk(m_);
not_full_.wait(lk, [&] { return q_.size() < cap_ || closed_; });
if (closed_) return false;
q_.push_back(std::move(value));
lk.unlock();
not_empty_.notify_one();
return true;
}
// Returns nullopt when closed and drained.
std::optional<T> pop() {
std::unique_lock<std::mutex> lk(m_);
not_empty_.wait(lk, [&] { return !q_.empty() || closed_; });
if (q_.empty()) return std::nullopt;
T value = std::move(q_.front());
q_.pop_front();
lk.unlock();
not_full_.notify_one();
return value;
}
void close() {
{
std::lock_guard<std::mutex> lk(m_);
closed_ = true;
}
not_empty_.notify_all();
not_full_.notify_all();
}
private:
const std::size_t cap_;
std::mutex m_;
std::condition_variable not_empty_;
std::condition_variable not_full_;
std::deque<T> q_;
bool closed_ = false;
};
19Kako izvodite migraciju šeme baze podataka za C++ servis bez prekida u radu?
Primenite faznu migraciju po obrascu proširenja i sužavanja (expand-contract). Prvo napravite izmene na šemi koje su kompatibilne sa prethodnim verzijama (backward-compatible), kao što su kolone koje prihvataju null vrednosti ili nove tabele koje ne remete rad aktivnog C++ servisa. Zatim postavite kompatibilan kod koji može da obrađuje i staru i novu reprezentaciju podataka, dopunite postojeće podatke u malim, kontrolisanim paketima (throttled batches), proverite konzistentnost, preusmerite čitanje na novu šemu i tek na kraju uklonite stare kolone ili kod kada nijedna pokrenuta verzija više ne zavisi od njih. Za velike tabele izbegavajte dugotrajne blokirajuće DDL operacije, koristite konkurentne/online operacije gde su podržane, pratite zaključavanja, replikaciju i resurse, i održavajte planove za vraćanje na prethodno stanje (rollback) koji funkcionišu i sa delimično postavljenim kodom i sa delimično migriranim podacima.
1. Expand: add new nullable column user_display_name.
2. Deploy compatible code: write both name and user_display_name; read new if present, else old.
3. Backfill: copy name -> user_display_name in small batches.
4. Validate: compare counts/checksums/null rates and monitor errors.
5. Switch: read only user_display_name after confidence.
6. Contract: stop writing old name, then drop old column in a later release.
20Kako C++ servis treba da čita podatke iz replika baze podataka uz upravljanje zastarelim čitanjima i garancijama tipa čitanje sopstvenih upisa (read-your-writes)?
Tretirajte čitanje sa replika kao eksplicitni kompromis po pitanju konzistentnosti. Čitanja koja tolerišu zastarelost mogu ići na zdrave replike, ali čitanje sopstvenih upisa (read-your-writes), transakciona čitanja ili čitanja kritična po pitanju svežine podataka treba usmeriti ka primarnoj bazi, osim ako je poznato da je izabrana replika već primenila relevantnu poziciju upisa, kao što su LSN (Log Sequence Number), vremenska oznaka (timestamp) ili verzija. Pratite kašnjenje (lag) i zdravlje replika, kodirajte traženi nivo konzistentnosti po krajnjoj tački (endpoint) ili zahtevu, i preusmerite na primarnu bazu (fallback), sačekajte sustizanje ili vratite grešku kada replike premaše dozvoljenu zastarelost. Dokumentujte model konzistentnosti kako bi pozivaoci znali koja čitanja mogu biti zastarela.
ReadPolicy EVENTUAL(max_staleness=2s):
choose healthy replica with lag <= 2s; else primary or error depending on SLA
ReadPolicy READ_YOUR_WRITES(session_lsn):
choose replica only if replay_lsn >= session_lsn;
otherwise wait briefly or route to primary
ReadPolicy STRONG:
route to primary