Qualcomm Interview Prep β 02. C++ & Object-Oriented ProgrammingΒΆ
Scope. The C++ language on top of C, and object-oriented design β classes/objects, the four OOP pillars, constructors/destructors, inheritance & polymorphism (vtable/vptr), references, const-correctness, operator overloading, the Rule of 0/3/5, copy/move semantics, RAII & smart pointers, templates & the STL, lambdas, the four casts, exceptions, namespaces, and the canonical OO patterns (Singleton, immutable class). Pure-C topics (raw pointers,
malloc/free, the memory map, storage classes, endianness,memcpy) live in01_c_programming.md; data-structure algorithms (linked lists, trees, LRU cache logic) in03_dsa.md; threads/mutex/semaphore/deadlock theory in04_os.md; low-level design & design-pattern catalogs in10_lld_system_design.md; kernel/driver/HAL in07_embedded_linux_kernel.md. Camera/ISP/multimedia is05_camera_isp_multimedia.md; ML in06_ml_deeplearning.md; computer-arch/digital in09_computer_arch_digital_design.md; puzzles in08_logical_puzzles_aptitude.md; behavioral/projects in11_behavioral_hr_projects.md. Overlaps are cross-linked, not duplicated.How to read each entry. Every question is answered in layers so you can stop at the depth you need: - Q β the question, phrased as interviewers actually ask it. - Frequency β how often it showed up in the 75 collected reports (tier + approximate count). - Concept β the basis β book-style fundamentals with worked examples and, where it helps, an SVG diagram. - The "wh"s β Why it exists (what problem the feature/rule solves), Where you see it (real Qualcomm/camera/embedded situations), and any important caveat. - Answer β a tight, say-it-out-loud interview answer. - Solution / good example β for "how do you implement/design/avoid X" questions, a complete, copy-pasteable pattern. - Follow-ups / gotchas β the traps interviewers spring next. - Seen in β the source reports.
Terms in bold-italics like vtable, slicing, RAII, rvalue reference, virtual destructor are defined in the Β§ Encyclopedia at the bottom β search there for any keyword.
Frequency legend (sample = 75 collected interview reports; counts are approximate and partly from aggregator pages, so treat them as directional): π₯π₯π₯ Very common (~8+ reports) Β· π₯π₯ Common (~4β7) Β· π₯ Occasional (~2β3) Β· β½ Foundational (rarely asked verbatim, but assumed and underpinning everything else).
Why C++/OOP dominates the Qualcomm loop. Camera HAL3, CamX/Chi, multimedia frameworks, and most Qualcomm system software are modern C++. Interviewers consistently probe polymorphism types, virtual functions, the vtable/vptr machinery, struct vs class, overloading vs overriding, friend, static, references vs pointers, the Singleton, immutable classes, smart pointers, and procedural-vs-OOP β often demanding code, not just definitions. Several reports describe 5β6 rounds that are "heavy C++." Evidence base: qualcomm_camera_interview_experiences.md.
Table of contentsΒΆ
- A. Classes, objects & the OO model β A1 class vs object Β· A2 four pillars Β· A3 procedural vs OOP Β· A4 access specifiers Β· A5 struct vs class
- B. Construction & lifetime β B1 ctors/dtors & order Β· B2 the Rule of 0/3/5 Β· B3 copy vs move semantics Β· B4 RAII
- C. References, const & static β C1 references vs pointers Β· C2 const member functions & const-correctness Β· C3 static members/methods Β· C4 pass arrays vs vectors
- D. Polymorphism & inheritance β D1 overloading vs overriding Β· D2 compile-time vs run-time (static vs dynamic binding) Β· D3 virtual functions, vtable & vptr Β· D4 pure virtual & abstract classes Β· D5 virtual destructors Β· D6 object slicing Β· D7 inheritance types Β· D8 diamond problem & virtual inheritance Β· D9 friend functions/classes
- E. Operators, resources & ownership β E1 operator overloading Β· E2 smart pointers & ownership Β· E3 Singleton (thread-safe) Β· E4 immutable class design
- F. Generics, STL & modern C++ β F1 templates & generic programming Β· F2 STL containers & complexities Β· F3 lambdas & function objects Β· F4 the four C++ casts Β· F5 exception handling & safety Β· F6 namespaces
- Β§ Encyclopedia β searchable glossary
- Β§ Last-5-minutes cheat sheet
A. Classes, objects & the OO modelΒΆ
A1 Β· Q: What is a class and what is an object? (And what actually happens in memory?)ΒΆ
Frequency: β½ Foundational β every OOP round assumes this; it underpins ~25 questions below.
Concept β the basis. A class is a user-defined type: a blueprint that bundles data members (state) with member functions (behavior) and an access policy. An object (or instance) is a concrete variable of that type β actual storage holding the members. The class is the cookie-cutter; objects are the cookies. Member functions are not stored per-object; they exist once and receive a hidden this pointer to the object they act on.
Example:
class Counter { // the TYPE (blueprint)
int value_ = 0; // data member (per-object state)
public:
void inc() { ++value_; } // behavior; 'this->value_' implied
int get() const { return value_; } // const: doesn't modify the object
};
Counter a, b; // TWO objects: a.value_ and b.value_ are independent storage
a.inc(); a.inc(); // a.value_ == 2
b.inc(); // b.value_ == 1 (separate object)
Why it exists. A class is the unit of encapsulation: it binds data to the operations allowed on it and hides the representation, so the rest of the program depends on an interface, not on internal fields. This tames complexity β you reason about "a FrameQueue" rather than a loose tangle of arrays and counters β and lets you change internals without breaking callers.
Where you see it (Qualcomm). A CameraDevice, ChiNode, ImageBuffer, or Sensor class wrapping a hardware resource; the Camera HAL3 C++ classes; a RequestQueue object per camera session. The hidden this pointer is exactly what lets one read() implementation serve thousands of buffer objects.
Answer. "A class is a user-defined type that groups data members and member functions together with an access policy; an object is an instance of that class β real storage holding its own copy of the non-static data members. Member functions are shared, not duplicated per object; they get a hidden this pointer to the instance. The class defines the interface and hides the representation β that's encapsulation."
Follow-ups / gotchas. sizeof an object counts its data members plus padding plus (if polymorphic) a vptr β not its functions. An empty class has sizeof >= 1 (so distinct objects have distinct addresses). static data members are shared across all objects (C3), not per-instance.
Seen in: Implicit in every OOP round; GfG #3/#12/#51 (OOP concepts), LeetCode C++ #62.
A2 Β· Q: What are the four pillars of OOP? Explain each with an example.ΒΆ
Frequency: π₯π₯π₯ Very common (~8+ reports) β asked directly ("4 main features/pillars of oops", "OOPs concepts: Abstraction, Encapsulation, Inheritance, Polymorphism").
Concept β the basis. OOP rests on four ideas:
1. Encapsulation β bundle data + the methods that operate on it into one unit and hide the internals behind private, exposing a controlled public interface.
2. Abstraction β expose what an object does, hide how. Model the essential behavior (often as an interface / pure-virtual contract) and suppress detail.
3. Inheritance β derive a new class from an existing one to reuse and extend it; models an "is-a" relationship.
4. Polymorphism β "many forms": one interface, many implementations. A Shape* call to area() runs the right derived version (run-time), or one function name serves many parameter types (compile-time).
Example tying all four together:
class Sensor { // ABSTRACTION: a contract
int id_; // ENCAPSULATION: hidden state
public:
explicit Sensor(int id) : id_(id) {}
int id() const { return id_; }
virtual int readPixel() = 0; // pure virtual: "what", not "how"
virtual ~Sensor() = default;
};
class ImxSensor : public Sensor { // INHERITANCE: ImxSensor is-a Sensor
public: using Sensor::Sensor;
int readPixel() override { /* talk to HW */ return 42; } // POLYMORPHISM
};
void capture(Sensor& s) { s.readPixel(); } // works for ANY Sensor subtype
Why it exists. The pillars are the discipline that makes large systems maintainable: encapsulation localizes change, abstraction lets callers ignore detail, inheritance avoids copy-paste, and polymorphism lets you add new types (a new sensor) without editing the code that uses them (the open/closed principle). Together they replace sprawling if/switch-on-type code with extensible class hierarchies.
Where you see it (Qualcomm). A Sensor/Node/Codec base class with concrete subclasses per chip/sensor; the framework calls a virtual process() and the right vendor implementation runs β add a sensor, don't touch the pipeline. Encapsulated ImageBuffer hides whether memory is ION/DMA-BUF.
Answer. "Encapsulation β bundle data with its methods and hide internals behind private, exposing a clean public interface. Abstraction β model what an object does and hide how, often via an interface or pure-virtual class. Inheritance β derive a class to reuse and extend a base, an 'is-a' relationship. Polymorphism β one interface, many forms: run-time via virtual functions through a base pointer, compile-time via overloading and templates. In a camera stack, a Sensor base with per-vendor subclasses overriding readPixel() shows all four."
Follow-ups / gotchas. Encapsulation β abstraction: encapsulation is the mechanism (access control bundling), abstraction is the design goal (hiding complexity). Some texts list only three (omit abstraction) β say "four pillars, sometimes three if abstraction is folded into encapsulation." A common follow-up: "give a real-world analogy" (a car: pedals = interface, engine = hidden).
Seen in: GfG #51, #24 ("4 main features/pillars of oops"), GfG #3/#12, AmbitionBox engineer reports.
A3 Β· Q: Compare procedural and object-oriented programming. When would you choose each?ΒΆ
Frequency: π₯π₯ Common (~3β4 reports) β asked explicitly ("Compare procedural and object-oriented programming with examples").
Concept β the basis. Procedural programming (C, classic) organizes a program as functions operating on passive data: data and the functions that touch it are separate; control flows top-down through procedure calls. Object-oriented programming organizes the program as objects that own their data and expose methods; data and behavior live together, and you build via encapsulation, inheritance, and polymorphism.
Example β same task, two styles:
/* PROCEDURAL (C): data is open; functions act on it */
typedef struct { int w, h; } Rect;
int area(const Rect *r) { return r->w * r->h; } // function separate from data
// OBJECT-ORIENTED (C++): data + behavior bundled, internals hidden
class Rect {
int w_, h_;
public:
Rect(int w, int h) : w_(w), h_(h) {}
int area() const { return w_ * h_; } // method owns the data
};
Why it matters. Procedural code is simple and direct β great for small, linear tasks and the lowest level (drivers, ISRs, hot loops). But as a system grows, free functions sharing global/struct data become tangled and hard to change. OOP adds structure: encapsulation limits the blast radius of changes, and polymorphism lets you extend behavior without rewriting callers. The trade-off is some indirection/overhead (virtual calls, more abstraction).
Where you see it (Qualcomm). The lowest layers (register access, DMA, RTOS tasks) are procedural C; the camera HAL/framework, CamX nodes, and app-facing services are OOP C++. A real codebase mixes both: a thin C driver under a C++ object model.
Answer. "Procedural programming structures code as functions operating on separate, passive data β top-down, like C. OOP structures code as objects that bundle data with the methods acting on it and adds encapsulation, inheritance, and polymorphism. Procedural is simpler and ideal at the metal β drivers, ISRs, tight loops. OOP scales better for large systems because it localizes change and lets you extend behavior without modifying existing callers. Qualcomm code mixes them: C at the driver level, C++ for the HAL/framework."
Follow-ups / gotchas. OOP isn't automatically "better" β misused, it adds needless indirection; for a 50-line tool, procedural is clearer. C can emulate OOP (structs of function pointers = vtables β see 01_c_programming.md A4). Related buzzwords they may probe: abstraction, data hiding, the SOLID principles (depth β 10_lld_system_design.md).
Seen in: GfG #12 ("Compare procedural and object-oriented programming with examples"), AmbitionBox OOPs-concept reports.
A4 Β· Q: Explain the access specifiers in C++. What's the default?ΒΆ
Frequency: π₯π₯ Common (~3β4 reports) β "Explain access specifiers with examples."
Concept β the basis. Access specifiers control who can name a member:
- public β accessible everywhere.
- private β accessible only inside the class's own members and its friends.
- protected β accessible inside the class, its friends, and derived classes (but not unrelated code).
They are the enforcement mechanism of encapsulation. The default is private for a class and public for a struct (the only language difference between them β see A5).
Example:
class Account {
long balance_ = 0; // private by default: nobody outside touches it directly
protected:
void audit() {} // visible to subclasses
public:
void deposit(long x) { if (x > 0) balance_ += x; } // controlled mutation
long balance() const { return balance_; } // read-only view
};
Why it exists. Access control lets the class enforce its invariants: if balance_ is private, the only way to change it is through deposit(), which can validate. Callers cannot reach in and corrupt state, and you can later change the representation without breaking them. protected is a middle ground for members a subclass legitimately needs.
Where you see it (Qualcomm). A Buffer class keeps its fd_/size_ private and exposes map()/unmap(); a base Node exposes protected hooks subclasses override; public API surfaces are deliberately minimal.
Answer. "public members are accessible anywhere, private only within the class and its friends, protected within the class, friends, and derived classes. They enforce encapsulation by restricting who can touch a member. The default is private for class and public for struct β that default is the only real difference between the two keywords. There's also access on inheritance itself β public/protected/private inheritance β which caps how inherited members are exposed."
Follow-ups / gotchas. Inheritance also has an access mode: class D : public B keeps B's public members public (the "is-a" you almost always want); private/protected inheritance is "implemented-in-terms-of" and rarely what you mean. friend (D9) bypasses access control deliberately. Access is per-class, not per-object β a member function can touch the private members of another object of the same class.
Seen in: GfG #3 ("Explain access specifiers with examples"), GfG #12.
A5 Β· Q: What's the difference between struct and class in C++?ΒΆ
Frequency: π₯π₯π₯ Very common (~6+ reports) β a Qualcomm staple ("struct vs class", "Difference between Structure vs union" leads here).
Concept β the basis. In C++ they are almost identical β both define a class type that can have data members, member functions, constructors, access specifiers, and inheritance. There are exactly two differences:
1. Default member access: struct members are public by default; class members are private by default.
2. Default inheritance access: struct D : Base inherits publicly by default; class D : Base inherits privately by default.
Example β these two are identical:
struct S { int x; }; // x is public
class C { public: int x; }; // same thing, written out
struct D1 : Base { }; // public inheritance (default for struct)
class D2 : public Base { }; // must write 'public' to match
Why it matters / convention. Because the language treats them the same, the difference is convention: use struct for passive aggregates (plain data, all public, no invariant to protect β a POD/"bag of fields"), and class for types with encapsulated state and behavior (private data, an interface to maintain invariants). This signals intent to readers and tools. (Contrast with C, where struct is only a data record with no methods β see 01_c_programming.md F3. And note struct vs union is a different question entirely β 01_c_programming.md.)
Where you see it (Qualcomm). Register/packet/descriptor layouts and POD config blobs are structs (often __attribute__((packed))); stateful subsystem types (CameraSession, BufferPool) are classes. HAL headers shared with C use struct so C can read them.
Answer. "In C++ struct and class are the same construct β both can have methods, constructors, access specifiers, and inheritance. The only differences are defaults: struct defaults to public members and public inheritance; class defaults to private and private inheritance. By convention I use struct for plain passive data with no invariants and class for types that encapsulate state and behavior. (In C, by contrast, a struct is purely a data record with no member functions.)"
Follow-ups / gotchas. A common trap: "can a struct have member functions / a constructor / inheritance in C++?" β yes. The C-vs-C++ difference (methods, default member access) is the real distinction interviewers want. class template parameters can be written template<class T> or template<typename T> β interchangeable there.
Seen in: LeetCode kernel SWE #15 ("struct vs class"), AmbitionBox #36, GfG #18, recurring across reports.
B. Construction & lifetimeΒΆ
B1 Β· Q: Explain constructors and destructors, and the order they run (especially with inheritance & members).ΒΆ
Frequency: π₯π₯ Common (~4 reports) β implied by ctor/dtor, Singleton (private dtor), and inheritance questions.
Concept β the basis. A constructor initializes an object when it's created; a destructor (~T()) cleans up when its lifetime ends. Kinds of constructor: default (T()), parameterized, copy (T(const T&)), and move (T(T&&)). The compiler generates the ones you don't.
Order of construction (deterministic): 1. Base classes, in declaration order of the base list. 2. Non-static data members, in the order they're declared in the class (not the order in the initializer list). 3. The constructor body.
Destruction is the exact reverse: body, then members (reverse declaration order), then bases (reverse). Use a member initializer list to construct members directly (vs assigning in the body).
Example:
struct Base { Base() { puts("Base ctor"); } ~Base() { puts("Base dtor"); } };
struct Member { Member() { puts("Member ctor"); } ~Member() { puts("Member dtor"); } };
struct Derived : Base {
Member m; // member
int x;
Derived(int v) : x(v) { // initializer list: x built here
puts("Derived body");
}
~Derived() { puts("Derived dtor"); }
};
// Construction: Base ctor -> Member ctor -> Derived body
// Destruction : Derived dtor -> Member dtor -> Base dtor (mirror image)
Why the order exists. A derived object contains a base subobject and its members, so those must be fully built before the derived constructor body can use them β and torn down after the derived body is done with them. Hence baseβmembersβbody up, and the strict reverse down. The reverse order guarantees a member is never destroyed while something constructed later still depends on it.
Where you see it (Qualcomm). A CameraSession whose members are a BufferPool and a Sensor β the pool/sensor must be alive before the session body runs and outlive it until teardown; ordering bugs here cause use-of-uninitialized or use-after-destroy. RAII (B4) leans entirely on this guaranteed order.
Answer. "Construction goes base classes first (in base-list order), then data members in declaration order, then the constructor body. Destruction is the exact reverse. Members are initialized in declaration order regardless of how you list them in the initializer list β so list them in declaration order to avoid surprises. I use the member initializer list to construct members directly instead of default-constructing then assigning."
Solution / good example β initializer-list pitfall:
struct Bad {
int a;
int b;
Bad(int v) : b(v), a(b) {} // BUG: 'a' is built FIRST (declared first) using
// uninitialized 'b' -> garbage. Order is by DECLARATION.
};
// Fix: order init-list to match declaration, and don't make one member depend on a later one.
Follow-ups / gotchas. Calling a virtual function inside a constructor/destructor does not dispatch to the derived override β during base construction the object "is" only a Base yet (its vptr points at Base's vtable). A constructor can't be virtual; a destructor often must be (D5). Mark single-arg constructors explicit to block surprising implicit conversions.
Seen in: LeetCode #2 (ctor/dtor implied via polymorphism+code), GfG #19 (Singleton with private destructor, new/delete), standard C++ expectation.
B2 Β· Q: What is the Rule of 0 / Rule of 3 / Rule of 5?ΒΆ
Frequency: π₯π₯ Common (~aggregator-reported; underpins copy/move & resource questions) β implied by the immutable-class, smart-pointer, and "deep copy" follow-ups.
Concept β the basis. These rules govern the special member functions that manage copying/moving/destruction:
- Rule of 3 (C++98): if you need a custom destructor, copy constructor, or copy-assignment operator, you almost certainly need all three β because their presence signals you manage a resource (raw pointer, file handle) that needs deep copy and proper release.
- Rule of 5 (C++11): add the move constructor and move-assignment operator for efficiency β moving steals resources instead of copying them.
- Rule of 0: the best rule β design classes that own no raw resources, delegating ownership to RAII members (std::string, std::vector, unique_ptr). Then you write none of the five; the compiler-generated ones are correct.
Example β Rule of 5 done right (a tiny owning buffer):
class Buf {
int* data_ = nullptr;
size_t n_ = 0;
public:
explicit Buf(size_t n) : data_(new int[n]), n_(n) {}
~Buf() { delete[] data_; } // 1 destructor
Buf(const Buf& o) : data_(new int[o.n_]), n_(o.n_) { // 2 copy ctor (deep)
std::copy(o.data_, o.data_ + n_, data_);
}
Buf& operator=(const Buf& o) { // 3 copy assign
if (this != &o) { Buf tmp(o); swap(tmp); } // copy-and-swap
return *this;
}
Buf(Buf&& o) noexcept : data_(o.data_), n_(o.n_) { // 4 move ctor (steal)
o.data_ = nullptr; o.n_ = 0;
}
Buf& operator=(Buf&& o) noexcept { // 5 move assign
if (this != &o) { delete[] data_; data_ = o.data_; n_ = o.n_;
o.data_ = nullptr; o.n_ = 0; }
return *this;
}
void swap(Buf& o) noexcept { std::swap(data_, o.data_); std::swap(n_, o.n_); }
};
class Buf { std::vector<int> data_; }; β done. No special members needed.
Why it exists. The compiler-generated copy is a member-wise (shallow) copy. For a class holding a raw pointer that's a disaster: two objects point at the same buffer, both delete it β double free / dangling. The Rule of 3/5 forces you to supply correct deep-copy/move/release together. The Rule of 0 sidesteps the whole problem by never owning raw memory.
Where you see it (Qualcomm). Any class wrapping a file descriptor, ION/DMA-BUF handle, GPU buffer, or mutex needs correct copy/move/destroy β or you leak handles / double-close. Most modern code targets Rule of 0 by holding unique_ptr/vector members.
Answer. "If a class manages a resource and needs a custom destructor, copy constructor, or copy assignment, it almost certainly needs all three (Rule of 3) β otherwise the default shallow copy double-frees or leaks. In C++11 you add the move constructor and move assignment for efficiency (Rule of 5). The ideal is the Rule of 0: own no raw resources β use vector, string, unique_ptr members β so the compiler-generated specials are correct and you write none of them."
Follow-ups / gotchas. Declaring any of the five can suppress generation of others (e.g. a user-declared destructor deprecates implicit copy and disables implicit move). Use = default / = delete to be explicit. Copy-and-swap gives strong exception safety and unifies copy assign. A move-from object must be left in a valid but unspecified state (destructible/assignable).
Seen in: Underpins LeetCode #62 / GfG #12 (immutable class, deep-copy questions), standard modern-C++ expectation.
B3 Β· Q: Explain copy vs move semantics (copy/move constructor & assignment). What is std::move?ΒΆ
Frequency: π₯π₯ Common (~aggregator-reported; copy-vs-deep-copy and "pass arrays/vectors" appear in reports).
Concept β the basis. Copy duplicates: the copy constructor/assignment makes the destination an independent clone (a deep copy for resource-owning types). Move transfers ownership: the move constructor/assignment steals the source's internal resource (e.g. a heap pointer) and leaves the source empty-but-valid β O(1) instead of O(n), no allocation. Move is selected when the source is an rvalue (a temporary, or something you've std::move'd). std::move doesn't move anything β it's just a cast to an rvalue reference (T&&) that makes the object eligible to be moved from.
Example:
std::vector<int> make();
std::vector<int> a = make(); // MOVE (the returned temporary is an rvalue)
std::vector<int> b = a; // COPY (a is an lvalue) -> duplicates the buffer
std::vector<int> c = std::move(a); // MOVE: steals a's buffer; 'a' is now empty-but-valid
// reading a.size() is fine (0); relying on its old contents is a bug.
Why it exists. Before C++11, returning or passing big containers by value copied megabytes pointlessly. Move semantics let ownership of the underlying buffer transfer without copying, which made return-by-value cheap and enabled move-only types like unique_ptr (you can't copy unique ownership, but you can move it). It's a core reason modern C++ is both safe and fast.
Where you see it (Qualcomm). Returning a filled std::vector<uint8_t> frame or a unique_ptr<Buffer> from a factory moves, not copies β critical when a "copy" would be a multi-megabyte image. Moving a unique_ptr<Node> into a pipeline transfers ownership cleanly.
Answer. "Copy makes an independent duplicate β a deep copy for resource-owning types. Move transfers ownership: it steals the source's internal resource (like a heap pointer) and leaves the source empty-but-valid, so it's O(1) and allocation-free. Move is chosen when the source is an rvalue β a temporary or something I've wrapped in std::move. std::move itself moves nothing; it's a cast to T&& that marks an object as movable. It powers cheap return-by-value and move-only types like unique_ptr."
Solution / good example β the move members (see also B2):
String(String&& o) noexcept : p_(o.p_), n_(o.n_) { // steal
o.p_ = nullptr; o.n_ = 0; // leave source valid/empty
}
String& operator=(String&& o) noexcept {
if (this != &o) { delete[] p_; p_ = o.p_; n_ = o.n_; o.p_ = nullptr; o.n_ = 0; }
return *this;
}
Follow-ups / gotchas. Mark move ops noexcept β otherwise std::vector reallocation falls back to copying for safety. Don't std::move a local you return β it's already an rvalue and move can defeat copy-elision/RVO. After a move the source is valid but unspecified; don't assume it's the old value. An lvalue has a name/address; an rvalue is a temporary. Cross-link: lvalue/rvalue basics β 01_c_programming.md.
Seen in: GfG #12 ("value manipulation when passing arrays and vectors"), standard modern-C++ expectation; copy-vs-deep-copy is a recurring probe.
B4 Β· Q: What is RAII? Why is it the central C++ idiom?ΒΆ
Frequency: π₯π₯ Common (~aggregator-reported; smart-pointer & resource questions assume it) β AmbitionBox #26 ("smart pointer") leads straight here.
Concept β the basis. RAII β Resource Acquisition Is Initialization β ties a resource's lifetime to an object's lifetime: you acquire the resource in the constructor and release it in the destructor. Because C++ guarantees the destructor runs when the object leaves scope β even on an exception or early return β the resource is always released exactly once, automatically. The resource can be heap memory, a file descriptor, a mutex lock, a GPU/DMA buffer, anything.
Example β a scoped lock and a scoped FD:
std::mutex m;
void f() {
std::lock_guard<std::mutex> lk(m); // RAII: locks now...
if (error()) return; // ...auto-unlocks here (no manual unlock)
risky(); // ...and even if risky() throws, unlock still runs
} // dtor unlocks on every exit path
class Fd { // RAII wrapper for a POSIX file descriptor
int fd_;
public:
explicit Fd(const char* p) : fd_(::open(p, O_RDONLY)) {}
~Fd() { if (fd_ >= 0) ::close(fd_); } // closed automatically
int get() const { return fd_; }
Fd(const Fd&) = delete; // non-copyable: one owner of the fd
Fd& operator=(const Fd&) = delete;
};
Why it exists. Manual cleanup (free, close, unlock) is fragile: any early return, break, or exception between acquire and release leaks the resource. C asks you to hand-write a goto cleanup ladder (see 01_c_programming.md C3). RAII automates that ladder with guaranteed destructor calls, eliminating leaks, double-frees, and forgotten unlocks by construction. It's the foundation of smart pointers, std::lock_guard, std::fstream, and exception safety.
Where you see it (Qualcomm). unique_ptr<Buffer>/shared_ptr for image buffers; lock_guard around a frame-queue mutex; RAII wrappers over ION/DMA-BUF handles and sensor power so a thrown exception or early-out never leaks a hardware handle. A long-running camera daemon that leaked one handle per frame would die in hours β RAII prevents it.
Answer. "RAII ties a resource's lifetime to an object's scope: acquire in the constructor, release in the destructor. Since C++ guarantees the destructor runs on every exit path β normal return, early return, or exception β the resource is always freed exactly once with no manual cleanup. It's how smart pointers, lock_guard, and file streams work, and it's what makes exception-safe, leak-free C++ possible. In C I'd hand-roll the same discipline with goto cleanup."
Follow-ups / gotchas. RAII + exceptions = automatic, exception-safe cleanup β the whole point. A destructor that releases resources should not throw (a throwing destructor during stack unwinding calls std::terminate). Move semantics let you transfer an RAII-owned resource (unique_ptr). Cross-link: the C goto cleanup analog β 01_c_programming.md; mutex/lock theory β 04_os.md.
Seen in: AmbitionBox #26 (smart pointer), Medium #4 (C++ mutex/semaphore); RAII underpins the smart-pointer/Singleton answers and is standard C++ expectation.
C. References, const & staticΒΆ
C1 Β· Q: Difference between a pointer and a reference β and which is better?ΒΆ
Frequency: π₯π₯π₯ Very common (~5+ reports) β asked directly ("Pointer vs reference β which is better?", "pass-by-value vs pass-by-reference").
Concept β the basis. A pointer is a variable holding an address: it can be null, reassigned to point elsewhere, and needs */-> to use. A reference is an alias for an existing object: it must be bound at initialization, can never be null or rebound, and is used with the object's own syntax. Under the hood a reference is usually implemented like a pointer, but the language rules differ.
Example:
int x = 1, y = 2;
int* p = &x; // pointer: may be null, reassignable
p = &y; // now points at y
*p = 9; // y == 9
int& r = x; // reference: bound to x forever
r = y; // does NOT rebind β assigns y's value INTO x (x == 2)
// int& bad; // ERROR: a reference must be initialized
void inc(int& n) { ++n; } // by reference: caller's variable changes
void incp(int* n) { ++(*n); } // by pointer: explicit, can pass nullptr
void noop(int n) { ++n; } // by value: a copy; caller unaffected
Why both exist. References give safe, clean pass-by-reference and operator syntax without null/rebind hazards β ideal for "must refer to a real object" parameters and for operator overloading (E1). Pointers give optionality and reseating: nullable ("maybe no object"), reassignable, usable in arithmetic and data structures (linked lists, dynamic arrays), and required for new/C APIs/hardware addresses. Neither is universally "better" β they solve different problems.
Where you see it (Qualcomm). Pass a large frame/struct by const& to avoid copying while forbidding mutation; use a pointer when the argument is optional (nullptr = "no metadata") or when interfacing with C driver APIs that traffic in pointers; references for operator overloads and range-for.
Answer. "A pointer holds an address β it can be null, reassigned, and needs explicit dereference; a reference is an alias to an existing object β bound once at initialization, never null, never rebound, used with normal syntax. Neither is universally better: I use a reference (often const&) when the parameter must refer to a real object and I want clean syntax and no copy, and a pointer when the value is optional (nullable), needs to be reseated, or interfaces with C APIs and hardware addresses. For passing big objects, pass by const& to avoid a copy."
Follow-ups / gotchas. You can't have a reference-to-reference, an array of references, or a null reference (forming one is undefined behavior). A const T& can bind to a temporary and extends its lifetime; a non-const T& cannot bind to an rvalue. T&& is an rvalue reference (for moves, B3) β different beast. Returning a reference to a local dangles, same as returning a pointer to a local (01_c_programming.md A6).
Seen in: LeetCode kernel SWE #15 ("Pointer vs reference β which is better?"), GfG #19 / Medium #45 (pass-by-value vs pass-by-reference with memory-level explanation).
C2 Β· Q: What is a const member function, and what is const-correctness?ΒΆ
Frequency: π₯ Occasional (~2 reports) β assumed for any C++ role; const keyword probed in driver rounds.
Concept β the basis. A const member function promises not to modify the object's observable state: int get() const;. Inside it, this is a pointer-to-const, so you can't assign to data members (unless they're mutable) or call non-const members. Const-correctness is the discipline of marking everything const that can be β parameters, references, methods β so the compiler enforces "read-only" intent.
Example:
class Image {
int w_, h_;
mutable int cachedArea_ = -1; // 'mutable' is writable even in const methods
public:
int width() const { return w_; } // read-only: callable on const Images
int area() const { // const, yet caches internally:
if (cachedArea_ < 0) cachedArea_ = w_ * h_; // allowed: cachedArea_ is mutable
return cachedArea_;
}
void resize(int w, int h) { w_ = w; h_ = h; cachedArea_ = -1; } // non-const: mutates
};
void print(const Image& img) { img.width(); /* img.resize(...) // ERROR: img is const */ }
Why it exists. const methods let a const object (or a const& parameter β the cheap way to pass big objects, C1) still be queried while the compiler guarantees it won't be mutated. This catches a whole class of bugs at compile time, documents intent, and is required to call any method on a const-qualified object. const and non-const overloads can also coexist (e.g. operator[] returning const T& vs T&).
Where you see it (Qualcomm). Accessors on ImageBuffer/FrameMetadata are const so they're callable on a const FrameMetadata& passed cheaply through the pipeline; a const method guarantees a query path won't accidentally mutate shared state.
Answer. "A const member function promises not to change the object's observable state, so it can be called on const objects and const& parameters; inside it this is pointer-to-const. Const-correctness is marking everything const that can be β methods, parameters, references β so the compiler enforces read-only intent and catches accidental mutation at compile time. The mutable keyword exempts a member (like a cache) from const, and you can overload on const to provide read vs write versions."
Follow-ups / gotchas. const is part of the function's signature, so get() and get() const are distinct overloads. mutable is the escape hatch for logically-const-but-physically-mutating members (caches, mutexes). const-ness is shallow: a const method can still mutate what a member pointer points at (the pointer is const, the pointee isn't). Casting away const and writing a truly-const object is undefined behavior (const_cast, F4). Pointer-placement of const (const T* vs T* const) β 01_c_programming.md B3.
Seen in: Pointer/const driver rounds; GfG #44/#19 (volatile/const keyword probing); standard C++ expectation.
C3 Β· Q: What are static members and static member functions?ΒΆ
Frequency: π₯π₯ Common (~4 reports) β "static methods and variables", "Static and Dynamic binding", "static keyword" recur.
Concept β the basis. A static data member belongs to the class, not to any object β there is exactly one shared instance for all objects, with static storage duration. A static member function has no this pointer: it's a regular function namespaced inside the class, can be called as Class::fn(), and can access only static members (not per-object data).
Example:
class Widget {
static int count_; // declaration: ONE shared counter for all Widgets
int id_;
public:
Widget() : id_(++count_) {} // each ctor bumps the shared count
static int count() { return count_; } // static method: no 'this', call Widget::count()
};
int Widget::count_ = 0; // DEFINITION (exactly once, in a .cpp) β pre-C++17
Widget a, b, c;
Widget::count(); // 3 (shared across all objects)
Why it exists. Some state is per-class, not per-object: an instance counter, a shared lookup table, a singleton's single instance, a factory registry. A static member models that without a global variable (it's encapsulated and namespaced). Static methods provide class-scoped helpers and factory functions that don't need an object. (This is the C++ analog of a C static file-scope variable β see 01_c_programming.md B2.)
Where you see it (Qualcomm). A shared sensor-capability table; an object/handle counter for leak diagnostics; the Singleton's static instance (E3); a static create() factory returning a configured object. A static constexpr table of gamma/tone constants in an ISP module.
Answer. "A static data member belongs to the class, not to any object β one shared copy for all instances, with program lifetime. A static member function has no this; it's a class-scoped function callable as Class::fn() that can only touch static members. They model per-class state and helpers β counters, shared tables, factory functions, the Singleton's single instance. Pre-C++17 a static data member needs an out-of-class definition; C++17 inline static lets you define it in the header."
Follow-ups / gotchas. "Static binding vs dynamic binding" is a different topic (D2) β don't confuse it with static members. A static local inside a member function is shared across all objects and lazily, thread-safely initialized (C++11 "magic statics" β the basis of the Meyers Singleton, E3). static constexpr members can be initialized in-class. Cross-link: C static (persistence + internal linkage) β 01_c_programming.md B2.
Seen in: GfG #12 ("static methods and variables"), GfG #18 ("Difference between static and dynamic binding"), GfG #19 (Singleton via static object), LeetCode #15 ("static keyword").
C4 Β· Q: What's the difference when you pass an array vs a std::vector to a function?ΒΆ
Frequency: π₯ Occasional (~2 reports) β asked explicitly ("Difference in value manipulation when passing arrays and vectors to functions").
Concept β the basis. A raw array decays to a pointer when passed: the function receives int*, loses the size (sizeof no longer gives the element count), and mutations affect the caller's array (it's effectively by-reference via the pointer). A std::vector is an object: passed by value it's copied (deep copy of all elements β expensive, caller unaffected); passed by reference (vector<int>&) it's shared and mutations are visible; passed by const& it's shared read-only with no copy.
Example:
void f(int a[], size_t n) { a[0] = 99; } // 'a' is really int*; caller's array changes
// sizeof(a) == sizeof(int*), NOT the array size!
void g(std::vector<int> v) { v[0] = 99; } // COPY: caller's vector is unchanged
void h(std::vector<int>& v) { v[0] = 99; } // REFERENCE: caller's vector changes
void k(const std::vector<int>& v) { /*read*/ } // const ref: no copy, read-only (preferred)
int arr[3] = {1,2,3}; f(arr, 3); // arr[0] becomes 99
std::vector<int> vec{1,2,3}; g(vec); // vec[0] still 1
Why it matters. The array-decay surprise (sizeof giving pointer size, silent mutation) is a classic bug source. With vectors you get a choice of semantics β copy vs share β but a by-value vector copy of a large image buffer is a hidden performance trap. The idiomatic rule: pass big objects by const& (read) or & (mutate), never by value unless you genuinely want a copy.
Where you see it (Qualcomm). Passing pixel data: a const std::vector<uint8_t>& (or a std::span) avoids copying a frame; accidentally passing by value would clone megabytes per call. Legacy C-style (uint8_t* buf, size_t len) is the array-decay convention.
Answer. "A raw array decays to a pointer when passed: the function gets a pointer, loses the size, and writes through it affect the caller's array β and sizeof inside the function gives the pointer's size, not the array's. A std::vector is a real object: by value it's deep-copied (caller unaffected, but potentially expensive), by reference mutations are shared, by const& it's read-only with no copy. For large data I pass by const& or & to avoid the copy."
Follow-ups / gotchas. Pass the size alongside a decayed array, or use std::span/std::array to keep size info. Range-for over a by-value vector copies; over const auto& it doesn't. std::array<T,N> (fixed size) does NOT decay and is copied by value. Cross-link: pointer arithmetic / array-pointer duality β 01_c_programming.md A5.
Seen in: GfG #12 ("Difference in value manipulation when passing arrays and vectors to functions").
D. Polymorphism & inheritanceΒΆ
D1 Β· Q: Difference between function overloading and overriding?ΒΆ
Frequency: π₯π₯π₯ Very common (~6+ reports) β asked verbatim across many reports ("method overloading vs overriding", "overloading vs overriding").
Concept β the basis. Overloading: multiple functions with the same name but different parameter lists in the same scope; the compiler picks one by the argument types at compile time. Overriding: a derived class redefines a virtual function of its base with the same name AND signature; the call is resolved by the object's dynamic type at run time via the vtable.
Example:
// OVERLOADING β same name, different params, chosen at COMPILE time
int area(int side) { return side*side; }
int area(int w, int h) { return w*h; }
double area(double r) { return 3.14159*r*r; }
area(4); // -> area(int)
area(3, 5); // -> area(int,int)
// OVERRIDING β redefine a virtual, chosen at RUN time
struct Shape { virtual double area() const { return 0; } virtual ~Shape() = default; };
struct Circle : Shape { double r;
double area() const override { return 3.14159*r*r; } }; // overrides Shape::area
Shape* s = new Circle{...};
s->area(); // -> Circle::area (dynamic dispatch)
Why each exists. Overloading gives one convenient name for conceptually-identical operations on different types (print(int), print(string)) and is the basis of operator overloading and many STL APIs. Overriding gives run-time polymorphism: code written against Shape* automatically calls the right derived behavior, so you add new shapes without changing the caller. They look similar but operate at opposite ends β compile-time vs run-time, same scope vs base/derived.
Where you see it (Qualcomm). Overloading: a configure(StreamConfig) vs configure(SensorMode) API. Overriding: the framework calls node->process(req) and the concrete IFEnode/IPEnode override runs β extensible pipeline by overriding, not editing.
Answer. "Overloading is multiple functions with the same name but different parameter lists in the same scope, resolved by argument types at compile time. Overriding is a derived class redefining a base's virtual function with the identical signature, resolved by the object's actual type at run time through the vtable. Overloading is compile-time (static) polymorphism; overriding is run-time (dynamic) polymorphism and requires inheritance plus virtual."
Follow-ups / gotchas. Return type alone can't overload. A derived function with the same name but different signature hides (not overrides) the base function β a classic trap; use using Base::f; or fix the signature, and write override so the compiler catches a mismatch. Overloading (compile-time) vs overriding (run-time) vs hiding (name lookup) are three distinct things. default arguments are bound statically even on virtual calls (another trap).
Seen in: GfG #3 ("overloading vs overriding"), GfG #12 ("method overloading vs overriding"), LeetCode #2 (polymorphism types), recurring across reports.
D2 Β· Q: What's the difference between compile-time and run-time polymorphism (static vs dynamic binding)?ΒΆ
Frequency: π₯π₯π₯ Very common (~5+ reports) β "Polymorphism β types, examples, and code", "static vs dynamic binding".
Concept β the basis. Polymorphism = "one interface, many forms." It comes in two flavors: - Compile-time / static polymorphism (early/static binding): the function called is decided at compile time. Achieved by function overloading, operator overloading, and templates. Zero run-time cost; no vtable. - Run-time / dynamic polymorphism (late/dynamic binding): the function called is decided at run time based on the object's actual type. Achieved by virtual functions dispatched through the vtable/vptr via a base pointer/reference.
Example:
template<class T> T maxv(T a, T b) { return a > b ? a : b; } // STATIC: maxv<int>, maxv<double>
// generated & bound at compile time
struct Base { virtual void f(){ puts("B"); } };
struct Der : Base { void f() override { puts("D"); } };
void call(Base& b){ b.f(); } // DYNAMIC: which f() depends on the real object at run time
call(*new Der); // prints "D"
Why both exist. Static polymorphism is free and fully inlinable β perfect for hot, type-known code (templates let you write generic algorithms with no overhead). Dynamic polymorphism is extensible across a binary boundary β the framework calls through a base interface and your plugin's override runs, even though the framework was compiled first. You trade a small per-call cost (an indirect call through the vtable) for run-time extensibility.
Where you see it (Qualcomm). Templates (static) for generic, zero-overhead container/algorithm code in performance paths; virtual interfaces (dynamic) for the HAL/INode/ICodec plugin model where vendor implementations are selected at run time.
Answer. "Compile-time polymorphism binds the call at compile time β function/operator overloading and templates β with no run-time cost. Run-time polymorphism binds at run time based on the object's dynamic type, via virtual functions dispatched through the vtable, and needs a base pointer or reference. So overloading and templates are static binding; virtual overriding is dynamic binding. Use static for hot, type-known code and dynamic where you need run-time extensibility through an interface."
Follow-ups / gotchas. A virtual call through a value (not pointer/reference) is bound statically and may slice (D6). Templates are sometimes called "parametric polymorphism." CRTP (curiously recurring template pattern) gives static polymorphism that looks like virtual dispatch. Static binding is also called early binding; dynamic = late binding.
Seen in: LeetCode #2 ("Polymorphism β types, examples, and code samples"), GfG #18 ("static vs dynamic binding"), GfG #3, LeetCode C++ #62.
D3 Β· Q: How do virtual functions work? Explain the vtable and vptr.ΒΆ
Frequency: π₯π₯π₯ Very common (~5+ reports) β explicitly "vtable, vptr", "Describe the virtual function in C++", "explain virtual functions with an example".
Concept β the basis. A virtual function enables run-time dispatch. The compiler implements it with two structures:
- vtable (virtual table): a per-class array of function pointers, one slot per virtual function, holding the addresses of that class's final overriders. Each polymorphic class has its own vtable.
- vptr (virtual pointer): a hidden pointer per object, set by the constructor to point at its class's vtable. It usually sits at the start of the object.
A virtual call p->f() becomes: load the object's vptr β index the vtable slot for f β call that address. So the same call site invokes different functions depending on the object's real type.
Example:
struct Shape { virtual double area() const = 0; virtual ~Shape() = default; };
struct Circle : Shape { double r; double area() const override { return 3.14159*r*r; } };
struct Square : Shape { double s; double area() const override { return s*s; } };
Shape* shapes[] = { new Circle{...}, new Square{...} };
for (Shape* sh : shapes)
sh->area(); // each object's vptr -> its class vtable -> Circle::area or Square::area
Why it exists. Virtual dispatch is the mechanism behind run-time polymorphism (D2): it lets the base declare an interface and each derived class supply behavior, so code written against Shape* automatically calls the correct override. The vtable/vptr indirection is how the machine decides at run time which function to run, with O(1) cost (a couple of pointer loads).
Where you see it (Qualcomm). Every plugin/HAL interface β camera3_device_ops in C is a hand-rolled vtable (a struct of function pointers, see 01_c_programming.md A4); in C++ the compiler builds the vtable for you. CamX INode/IChiNode virtual interfaces dispatch to the right node type per frame.
Answer. "Marking a function virtual makes the compiler build a vtable β a per-class array of pointers to that class's virtual function overrides β and give each object a hidden vptr set by the constructor to point at its class's vtable. A virtual call loads the vptr, indexes the vtable, and calls that address, so the same call site dispatches to the right derived function based on the object's actual type at run time. It costs a couple of pointer loads and an indirect call, and the call can't be inlined."
Follow-ups / gotchas. vptr is set during construction as each level runs, which is why a virtual call inside a base constructor dispatches to the base version (B1). A polymorphic object is bigger by one pointer (the vptr). Non-virtual calls bind statically and can inline. The exact vtable layout is implementation-defined (the Itanium ABI is common). RTTI (dynamic_cast/typeid) hangs off the vtable too (F4).
Seen in: LeetCode C++ #62 ("vtable, vptr"), GfG #3/#9 ("virtual functions"), foundit #9 ("Describe the virtual function"), AmbitionBox, multiple reports.
D4 Β· Q: What is a pure virtual function and an abstract class? What is an interface?ΒΆ
Frequency: π₯π₯ Common (~3β4 reports) β "abstraction, virtual classes", "explain abstraction".
Concept β the basis. A pure virtual function is declared = 0 and (usually) has no body in the base β it says "every concrete subclass must provide this." A class with at least one pure virtual function is an abstract class: it cannot be instantiated, only inherited from and used through a pointer/reference. An interface (no language keyword in C++) is the common idiom of an abstract class with all pure virtuals and no data β a pure contract.
Example:
class Codec { // abstract base = a contract
public:
virtual void encode(const Frame&) = 0; // pure virtual: subclass MUST implement
virtual void flush() = 0;
virtual ~Codec() = default; // virtual dtor (D5)
};
// Codec c; // ERROR: cannot instantiate an abstract class
class H264Codec : public Codec {
void encode(const Frame&) override { /* ... */ }
void flush() override { /* ... */ }
};
std::unique_ptr<Codec> c = std::make_unique<H264Codec>(); // use through the interface
Why it exists. Pure virtuals express abstraction in code: the base defines what operations exist without committing to how, and the language enforces that every concrete subclass implements them (forget one β it stays abstract β won't compile when you try to instantiate). This is how you define plugin contracts and program to an interface, not an implementation.
Where you see it (Qualcomm). INode, ISensor, ICodec, HAL3 interfaces β abstract bases whose pure virtuals the vendor/sensor-specific subclass fills in. The framework holds unique_ptr<INode> and never knows the concrete type.
Answer. "A pure virtual function is declared = 0 and forces every concrete subclass to implement it. A class with any pure virtual function is abstract β it can't be instantiated, only used as a base through a pointer or reference. An interface is the idiom of an abstract class that's all pure virtuals with no data β a pure contract. They express abstraction and let me program to an interface; the compiler guarantees subclasses honor the contract."
Follow-ups / gotchas. A pure virtual can still have a definition (callable via Base::f()), useful for shared default behavior. If a subclass leaves any pure virtual unimplemented, it too is abstract. Always give an abstract base a virtual destructor (D5). "Abstract class vs interface": in C++ both are abstract classes; the distinction (data + some implementation vs pure contract) is convention, sharper in Java/C#.
Seen in: LeetCode C++ #62 ("abstraction, virtual classes"), GfG #12 ("abstraction"), AmbitionBox OOPs reports.
D5 Β· Q: Why do you need a virtual destructor?ΒΆ
Frequency: π₯π₯ Common (~3β4 reports) β a classic follow-up to vtable/inheritance questions.
Concept β the basis. If you delete a derived object through a base-class pointer and the base destructor is not virtual, the behavior is undefined β in practice only the base destructor runs, the derived part is never cleaned up β resource/memory leak (and possibly worse). Making the base destructor virtual puts the destructor in the vtable so delete basePtr dispatches to the derived destructor first, then the base's β the whole object is destroyed correctly.
Example:
struct Base { /* no virtual dtor */ ~Base() { puts("~Base"); } };
struct Derived : Base { int* buf = new int[100]; ~Derived(){ delete[] buf; puts("~Derived"); } };
Base* p = new Derived;
delete p; // NON-virtual dtor -> UB; typically only "~Base" runs -> buf LEAKS
// Fix:
struct Base2 { virtual ~Base2() { puts("~Base2"); } };
struct Derived2 : Base2 { int* buf = new int[100]; ~Derived2(){ delete[] buf; } };
Base2* q = new Derived2;
delete q; // virtual dtor -> ~Derived2 then ~Base2 -> buf freed correctly
Why it exists. Polymorphic deletion (delete basePtr) is common β you hold objects through base pointers precisely so you don't depend on the concrete type. For that to clean up correctly, destruction must also be polymorphic. The virtual destructor is the rule that makes "own a Derived through a Base*" safe.
Where you see it (Qualcomm). Any factory returning unique_ptr<INode>/Base* β without a virtual base destructor, destroying the node leaks the derived class's buffers/handles. This is a real, recurring camera-daemon leak source, which is why interviewers love it.
Answer. "If you delete a derived object through a base-class pointer and the base destructor isn't virtual, it's undefined behavior β typically only the base destructor runs and the derived part leaks. Declaring the base destructor virtual makes deletion dispatch through the vtable to the derived destructor first, then up the chain, so the whole object is destroyed. Rule of thumb: any class meant to be used polymorphically β i.e. that has virtual functions β needs a virtual destructor."
Follow-ups / gotchas. If you add any virtual function you almost always want a virtual ~T() = default;. Cost: the class gains a vptr (it's already polymorphic, so no extra cost if it has other virtuals). A protected non-virtual destructor is an alternative for bases never deleted polymorphically (prevents delete basePtr at compile time). unique_ptr<Base> calling delete needs the virtual dtor; shared_ptr can store a type-erased deleter that side-steps it (subtle).
Seen in: Follow-up to LeetCode C++ #62 / GfG vtable questions; standard, heavily-expected C++ trap.
D6 Β· Q: What is object slicing?ΒΆ
Frequency: π₯ Occasional (~aggregator-reported) β a classic polymorphism gotcha that follows vtable/inheritance questions.
Concept β the basis. Object slicing happens when you copy a derived object into a base object by value: only the base portion is copied; the derived-specific members are "sliced off," and the result is a plain Base β including its vptr, so virtual calls dispatch to Base, not the original derived type.
Example:
struct Shape { virtual double area() const { return 0; } };
struct Circle : Shape { double r = 2; double area() const override { return 3.14159*r*r; } };
Circle c;
Shape s = c; // SLICING: only the Shape part is copied; r is lost
s.area(); // -> Shape::area() == 0, NOT Circle::area()
void draw(Shape sh); // by VALUE -> slices any derived argument
draw(c); // the Circle is sliced to a Shape inside draw()
Why it matters. Slicing silently breaks polymorphism: you think you're holding a Circle, but you have a Shape. It's a subtle bug because it compiles cleanly and "works" β just with the wrong (base) behavior. The fix is to never hold polymorphic objects by value β use a base reference, pointer, or smart pointer, which preserves the dynamic type and its vptr.
Where you see it (Qualcomm). Storing std::vector<Shape> (by value) instead of std::vector<std::unique_ptr<Shape>> slices every element to the base β the per-frame node behavior collapses to the base's no-op. Passing a derived Request by value into a base parameter loses its specialization.
Answer. "Object slicing is when you assign or copy a derived object into a base object by value β only the base subobject is copied, the derived members are sliced off, and the copy's vptr points at the base vtable, so virtual calls go to the base. It silently defeats polymorphism. I avoid it by handling polymorphic objects through a base reference, pointer, or smart pointer, never by value β for example vector<unique_ptr<Shape>> instead of vector<Shape>."
Follow-ups / gotchas. Passing by const Base& instead of Base avoids slicing and the copy. Containers of base values slice; containers of pointers/smart-pointers don't. You can disable slicing by making the base abstract (can't instantiate it) or deleting its copy operations. Related: the layout diagram shows why β the base sits at offset 0, so a value-copy grabs only that prefix.
Seen in: Standard polymorphism gotcha; follows the vtable/inheritance questions in LeetCode C++ #62 / GfG #3.
D7 Β· Q: What are the types of inheritance in C++?ΒΆ
Frequency: π₯π₯ Common (~3β4 reports) β "Types of inheritance in C++ and resolution of ambiguity in multiple inheritance".
Concept β the basis. By shape of the hierarchy:
- Single β one base, one derived (B β D).
- Multilevel β a chain (A β B β C).
- Hierarchical β many derived from one base (Shape β {Circle, Square}).
- Multiple β one derived from several bases (class D : public A, public B).
- Hybrid β a mix (e.g. multiple + hierarchical), which is where the diamond problem arises (D8).
Independently, by access mode: public (is-a β the usual), protected, private (implemented-in-terms-of).
Example:
struct A {};
struct B : A {}; // single
struct C : B {}; // multilevel (A->B->C)
struct D1 : A {}; struct D2 : A {}; // hierarchical (both from A)
struct M : A, D1 {}; // multiple (two bases)
Why it exists. Inheritance models is-a relationships and reuses base behavior/state. Different shapes capture different designs: hierarchical for a family of variants (shapes, sensors), multilevel for layered refinement, multiple for combining orthogonal capabilities (e.g. a class that is both Serializable and Drawable). C++ allows multiple inheritance (unlike Java), which is powerful but introduces ambiguity that virtual inheritance resolves.
Where you see it (Qualcomm). Hierarchical sensor/node/codec families (one base, many concrete types); multiple inheritance to mix interfaces (class IfeNode : public INode, public IConfigurable). Mixins/policy classes combine behavior.
Answer. "By hierarchy shape: single (one base), multilevel (a chain AβBβC), hierarchical (many derived from one base), multiple (one class from several bases), and hybrid (a combination). And by access: public inheritance for is-a, private/protected for implemented-in-terms-of. C++ supports multiple inheritance, which can create the diamond problem when two bases share a common ancestor β resolved with virtual inheritance."
Follow-ups / gotchas. Prefer composition over inheritance when it's not truly is-a. Multiple inheritance of two concrete classes is usually a design smell; multiple inheritance of interfaces is common and fine. The ambiguity follow-up (D8) is the real target. Java/C# allow only single class inheritance + multiple interfaces β a common comparison.
Seen in: GfG #12 ("Types of inheritance in C++ and resolution of ambiguity in multiple inheritance").
D8 Β· Q: What is the diamond problem, and how does virtual inheritance solve it?ΒΆ
Frequency: π₯π₯ Common (~3 reports) β "resolution of ambiguity in multiple inheritance".
Concept β the basis. The diamond problem arises with multiple inheritance when two intermediate classes share a common base: B and C each inherit from A, and D inherits from both B and C. Now a D object contains two separate A subobjects, so d.member (from A) is ambiguous, and you carry duplicate base state. Virtual inheritance (class B : virtual public A) tells the compiler that all paths should share one common A subobject β eliminating the duplication and ambiguity.
Example:
struct Animal { int age = 0; };
struct Mammal : virtual Animal {}; // 'virtual' -> shared Animal
struct WingedAnimal : virtual Animal {}; // 'virtual' -> shared Animal
struct Bat : Mammal, WingedAnimal {}; // ONE Animal subobject
Bat b;
b.age = 3; // unambiguous: there is a single Animal
// Without 'virtual', b.age is ambiguous: Mammal::Animal::age vs WingedAnimal::Animal::age
// -> must write b.Mammal::age, and you'd have TWO ages.
Why it exists. Multiple inheritance is useful (combining capabilities), but a shared ancestor inherited via two paths duplicates state and makes member access ambiguous. Virtual inheritance solves it by guaranteeing a single shared instance of the common base, so the class hierarchy behaves the way the design intends (one Animal per Bat). The cost is extra indirection (a vbase pointer/offset) and that the most-derived class must construct the virtual base.
Where you see it (Qualcomm). Rare in well-designed C++ (people prefer composition/interfaces to avoid it), but it appears in interface hierarchies where a common base interface is inherited via multiple mixins. Knowing the mechanism signals you understand multiple inheritance deeply.
Answer. "The diamond problem is multiple inheritance where two parents share a common base, so the most-derived class ends up with two copies of that base β making its members ambiguous and duplicating state. Virtual inheritance, class B : virtual public A, makes all paths share a single instance of the common base, removing the duplication and ambiguity. The trade-off is extra indirection, and the most-derived class becomes responsible for constructing the virtual base."
Follow-ups / gotchas. Without virtual inheritance you can still disambiguate explicitly (b.Mammal::age), but you keep two subobjects. Virtual base construction order and initialization is subtle β the most-derived class initializes the virtual base directly. Many designs avoid the diamond entirely by favoring composition or single inheritance + interfaces. Don't confuse virtual inheritance with virtual functions β different uses of the keyword.
Seen in: GfG #12 ("resolution of ambiguity in multiple inheritance"); standard MI question.
D9 Β· Q: What are friend functions and friend classes? When would you use them?ΒΆ
Frequency: π₯π₯ Common (~3β4 reports) β asked explicitly ("friend keyword", "Friend classes and their use").
Concept β the basis. A friend declaration grants a specific external function or class access to a class's private and protected members. Friendship is granted by the class (you declare the friend inside it), is not transitive (a friend of a friend isn't a friend), not inherited, and not symmetric (A friending B doesn't make B friend A).
Example:
class Vector2 {
double x_, y_;
public:
Vector2(double x, double y) : x_(x), y_(y) {}
// friend free function: can read private x_, y_ of BOTH operands
friend Vector2 operator+(const Vector2& a, const Vector2& b) {
return Vector2(a.x_ + b.x_, a.y_ + b.y_);
}
friend std::ostream& operator<<(std::ostream&, const Vector2&); // friend declaration
friend class Serializer; // Serializer may touch Vector2's privates
};
std::ostream& operator<<(std::ostream& os, const Vector2& v) { // definition
return os << '(' << v.x_ << ',' << v.y_ << ')'; // accesses privates
}
Why it exists. Some operations are conceptually part of a class's interface but can't be members β notably binary operators where the left operand isn't your class (os << v, or symmetric a + b where you want both operands treated equally, and implicit conversions on both sides). friend lets such free functions access internals without exposing public getters that would weaken encapsulation. A friend class lets a tightly-coupled helper (a builder, serializer, iterator, or test) reach internals deliberately.
Where you see it (Qualcomm). operator<< for logging an internal struct; a Builder/Serializer friend that constructs/inspects a tightly-coupled object; unit-test access to internals. Used sparingly β over-friending breaks encapsulation.
Answer. "A friend function or class is granted access to a class's private and protected members. Friendship is granted by the class, isn't transitive, inherited, or symmetric. I use it where an operation belongs to the interface but can't be a member β like operator<< whose left operand is a stream, or symmetric binary operators β or for a tightly-coupled helper like a builder or serializer that legitimately needs internal access. It's deliberate, controlled access, used sparingly so it doesn't erode encapsulation."
Follow-ups / gotchas. friend breaks encapsulation by design, so overuse is a smell β prefer public methods when reasonable. A friend declared inside a class is not a member of it. operator<</operator>> are the textbook friend use because the stream must be the left operand. Cross-link: operator overloading β E1.
Seen in: LeetCode Display #2 ("the virtual and friend keywords"), LeetCode kernel SWE #15 ("Friend classes and their use"); recurring Qualcomm question.
E. Operators, resources & ownershipΒΆ
E1 Β· Q: What is operator overloading? How and when do you use it?ΒΆ
Frequency: π₯ Occasional (~aggregator-reported; appears via friend/operator questions).
Concept β the basis. Operator overloading lets you define what built-in operators (+, ==, <<, [], (), =, etc.) mean for your own types, so user-defined types read like built-ins. You implement an operator as a member function (left operand is your object) or a non-member/friend function (when the left operand isn't your type, e.g. streams, or for symmetric conversions).
Example:
class Complex {
double re_, im_;
public:
Complex(double r=0, double i=0) : re_(r), im_(i) {}
Complex operator+(const Complex& o) const { // member: a + b
return Complex(re_ + o.re_, im_ + o.im_);
}
bool operator==(const Complex& o) const { // comparison
return re_ == o.re_ && im_ == o.im_;
}
friend std::ostream& operator<<(std::ostream& os, const Complex& c) { // non-member
return os << c.re_ << "+" << c.im_ << "i"; // stream must be left operand
}
};
Complex a{1,2}, b{3,4};
Complex c = a + b; // operator+
std::cout << c; // operator<< -> "4+6i"
Why it exists. For math/value types (complex numbers, matrices, vectors, fixed-point pixels) and container-like types, overloaded operators give natural, readable syntax (a + b, m[i], *it) instead of verbose method calls (a.plus(b)). The STL relies on it pervasively: iterators overload */++/==, smart pointers overload */->, std::string overloads +/==.
Where you see it (Qualcomm). A Fixed/Pixel/Matrix3x3 type with arithmetic operators for ISP color math; operator<< for logging; iterator and smart-pointer operators everywhere in modern code.
Answer. "Operator overloading defines what operators like +, ==, [], << mean for your own types so they read like built-ins. You write them as member functions when the left operand is your object, or as non-member/friend functions when it isn't β operator<< is a friend because the stream is the left operand. It's ideal for value types like vectors, matrices, and complex numbers, and it's what makes STL iterators, std::string, and smart pointers feel natural. The guideline is to overload only where the meaning is obvious and consistent with built-in semantics."
Follow-ups / gotchas. Don't overload operators with surprising semantics (don't make + subtract). Some operators must be members (=, [], (), ->). C++20 adds the spaceship operator<=> to auto-generate comparisons. Overload assignment per the Rule of 3/5 (B2). operator-> chains until it returns a raw pointer (smart-pointer trick).
Seen in: Implied by friend/operator questions (LeetCode #2, #15); standard C++ topic.
E2 Β· Q: Explain smart pointers β unique_ptr, shared_ptr, weak_ptr β and ownership.ΒΆ
Frequency: π₯π₯ Common (~aggregator-reported; AmbitionBox #26 asks "Explain about smart pointer" directly).
Concept β the basis. Smart pointers are RAII wrappers (B4) that own a heap object and free it automatically β no manual delete, no leaks/double-frees. Three flavors model ownership:
- std::unique_ptr<T> β exclusive ownership: exactly one owner; move-only (can't copy, can transfer with std::move); zero overhead vs a raw pointer. The default choice.
- std::shared_ptr<T> β shared ownership: a reference count tracks owners; the object is freed when the last shared_ptr is destroyed. The count is atomic (thread-safe refcounting), so it's heavier.
- std::weak_ptr<T> β a non-owning observer of a shared_ptr-managed object; doesn't affect the count; lock() yields a shared_ptr if the object is still alive or null if gone. Used to break reference cycles.
Example:
auto u = std::make_unique<Buffer>(1920*1080); // sole owner; freed at scope exit
auto consume = [](std::unique_ptr<Buffer> b){ /* now owns it */ };
consume(std::move(u)); // transfer ownership; u is now null
auto s1 = std::make_shared<Buffer>(640*480); // refcount = 1
auto s2 = s1; // refcount = 2 (shared)
// object freed when both s1 and s2 die
std::weak_ptr<Buffer> w = s1; // observes, doesn't own
if (auto sp = w.lock()) { /* still alive */ } // safe access
Why it exists. Raw new/delete is error-prone: forget delete β leak; delete twice β corruption; delete then use β use-after-free. Smart pointers make ownership explicit in the type and automate cleanup via RAII, eliminating these bugs. unique_ptr expresses "one owner," shared_ptr "many owners, free when last leaves," weak_ptr "watch without owning." This is the modern replacement for manual heap management.
Where you see it (Qualcomm). unique_ptr<Node>/unique_ptr<Buffer> for sole-owned pipeline objects passed by move; shared_ptr<FrameMetadata> when several pipeline stages reference one frame and it must live until the last reader finishes; weak_ptr from a child node back to its parent to avoid a parentβchild cycle that would leak.
Answer. "Smart pointers are RAII wrappers that own heap memory and free it automatically. unique_ptr is exclusive ownership β one owner, move-only, zero overhead β and it's my default. shared_ptr is shared ownership via an atomic reference count; the object dies when the last shared_ptr does. weak_ptr is a non-owning observer of a shared object that breaks reference cycles β you lock() it to get a shared_ptr if the object's still alive. They make ownership explicit and eliminate leaks, double-frees, and use-after-free."
Solution / good example β breaking a cycle with weak_ptr:
struct Node {
std::shared_ptr<Node> next; // owns the next node
std::weak_ptr<Node> prev; // OBSERVES the previous β NOT shared_ptr,
}; // otherwise next<->prev cycle never frees (leak)
Follow-ups / gotchas. Prefer make_unique/make_shared (exception-safe, one allocation for shared). Two shared_ptrs pointing at each other = a reference cycle = leak β make one a weak_ptr. shared_ptr's control block is atomic (cost); the pointee's data is not thread-safe. Don't build two shared_ptrs from the same raw pointer (double control block β double free). unique_ptr can hold a custom deleter (e.g. to close() an fd). Cross-link: RAII β B4; refcount/atomics β 04_os.md.
Seen in: AmbitionBox System Engineer #26 ("Explain about smart pointer"); standard modern-C++ expectation, frequently a follow-up to memory-management questions.
E3 Β· Q: Implement a Singleton β and make it thread-safe.ΒΆ
Frequency: π₯π₯π₯ Very common (~5+ reports) β asked verbatim with code ("Implement a Singleton class and make it thread-safe (write the code)", "Create a Singleton Class").
Concept β the basis. The Singleton pattern ensures a class has exactly one instance with a single global access point. You make the constructor private, delete copy/move, and expose a static instance() accessor. The classic problem is thread safety of the lazy initialization: if two threads call instance() simultaneously the first time, a naive check-then-create can construct two objects.
The modern, correct answer is the Meyers Singleton: a function-local static. Since C++11, initialization of a function-local static is guaranteed thread-safe ("magic statics") β the compiler inserts a one-time, race-free guard. It's lazy, leak-free (no new), and minimal.
Example β Meyers Singleton (the answer to give first):
class Logger {
public:
static Logger& instance() {
static Logger inst; // C++11: initialized exactly once, thread-safely
return inst;
}
void log(const char* msg) { /* ... */ }
Logger(const Logger&) = delete; // non-copyable
Logger& operator=(const Logger&) = delete; // non-assignable
private:
Logger() = default; // private ctor: nobody else can construct one
~Logger() = default;
};
Logger::instance().log("hi"); // single shared instance
Why it exists / why thread-safe matters. Some resources are inherently single: a logging sink, a config registry, a hardware-manager singleton. The pattern centralizes access. But lazy instance() is a shared mutable initialization β a data race without synchronization. C++11 magic statics give you correctness for free; pre-C++11 you needed explicit locking, which leads to the famous double-checked locking pattern.
Where you see it (Qualcomm). A camera-subsystem manager, a global config/registry, a logging service, a single ISP-resource arbiter β accessed from many threads, so the thread-safe single-instance guarantee is exactly the requirement.
Answer. "Make the constructor private, delete copy and move, and expose a static instance(). The cleanest thread-safe version is the Meyers Singleton: a function-local static returned by reference β since C++11 the standard guarantees that local static is initialized exactly once, thread-safely, so no manual locking is needed. It's lazy and leak-free. If I had to support pre-C++11 or a heap instance, I'd use double-checked locking with a mutex and an atomic flag."
Solution / good example β double-checked locking (the pre-C++11 / heap variant they sometimes want):
#include <atomic>
#include <mutex>
class Singleton {
static std::atomic<Singleton*> instance_;
static std::mutex mtx_;
Singleton() = default;
public:
static Singleton* instance() {
Singleton* p = instance_.load(std::memory_order_acquire);
if (!p) { // 1st check (no lock, fast path)
std::lock_guard<std::mutex> lk(mtx_);
p = instance_.load(std::memory_order_relaxed);
if (!p) { // 2nd check (under lock)
p = new Singleton();
instance_.store(p, std::memory_order_release);
}
}
return p;
}
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mtx_;
Follow-ups / gotchas. Always lead with the Meyers Singleton β it's simpler and correct; mention DCLP only to show you know the history and why it needs atomics. Singletons are also criticized (global state, hard to test, hidden dependencies) β note you'd use dependency injection where testability matters. Delete copy/move or you can clone the "single" instance. Cross-link: mutex/atomics/memory-ordering β 04_os.md; pattern catalog β 10_lld_system_design.md.
Seen in: LeetCode C++ #62 ("Implement a Singleton class and make it thread-safe β write the code"), GfG #19 ("Create a Singleton Class using private destructor, static object"), LeetCode kernel SWE #15 (design patterns).
E4 Β· Q: Implement your own immutable class.ΒΆ
Frequency: π₯π₯ Common (~aggregator-reported; asked verbatim) β "Implement your own immutable class."
Concept β the basis. An immutable object cannot be changed after construction β all state is set once in the constructor and never mutated. In C++ you achieve it by: making data members private and either const or only-set-in-the-ctor; providing only const accessors (no setters); and, if it holds references to mutable objects, deep-copying them in and never handing out non-const references to internals.
Example:
class ImmutablePoint {
const int x_; // const members: fixed at construction
const int y_;
public:
ImmutablePoint(int x, int y) : x_(x), y_(y) {} // set once
int x() const { return x_; } // read-only accessors only
int y() const { return y_; }
// "mutation" returns a NEW object instead of changing this one:
ImmutablePoint withX(int nx) const { return ImmutablePoint(nx, y_); }
};
class ImmutableConfig {
const std::vector<int> data_; // const: can't reassign or mutate the vector
public:
explicit ImmutableConfig(std::vector<int> d) : data_(std::move(d)) {} // copy in
int at(size_t i) const { return data_.at(i); } // read-only
std::vector<int> snapshot() const { return data_; } // hand out a COPY, not a ref
};
Why it exists. Immutable objects are inherently thread-safe (no writes β no data races β no locking needed when shared across threads), easier to reason about, safely shareable/cacheable, and good hash-map keys. Standard examples: std::string semantics in some languages, Java's String/Integer. In a multithreaded camera/SoC system, immutable config/metadata objects can be shared between threads with zero synchronization.
Where you see it (Qualcomm). Immutable StreamConfig/SensorMode/FrameMetadata snapshots shared across pipeline threads without locks; a captured request's parameters frozen at submit time so concurrent stages see a consistent view.
Answer. "I make all data members private and const (or set-only-in-the-constructor), provide only const accessors and no setters, and if the object holds mutable members like a vector, I deep-copy them in and only ever return copies β never a non-const reference to internals. 'Mutations' return a new object rather than modifying this one. The payoff is thread safety with no locking, since a never-written object can't race, plus easier reasoning and safe sharing."
Follow-ups / gotchas. const members make the class non-assignable and non-movable in the usual sense (you can't reassign a const member) β often fine, but note it. Holding a const pointer/reference to a mutable external object doesn't make you immutable (the pointee can still change) β that's why you copy in. Returning const& to an internal vector is usually OK (caller can't mutate it) but exposes lifetime coupling; a snapshot copy is safest. Cross-link: thread safety β 04_os.md.
Seen in: GfG Associate SWE #12 ("Implement your own immutable class"); recurring OOD probe.
F. Generics, STL & modern C++ΒΆ
F1 Β· Q: What are templates and generic programming?ΒΆ
Frequency: π₯ Occasional (~aggregator-reported; underpins STL questions).
Concept β the basis. A template is a blueprint from which the compiler generates concrete functions/classes for the types you use β generic programming: write the algorithm once, get a type-specialized, fully type-checked version per type, with no run-time overhead. Function templates generalize over parameter types; class templates generalize over member types (every STL container is a class template).
Example:
template<typename T>
T maxValue(T a, T b) { return (a > b) ? a : b; } // one definition...
maxValue(3, 7); // compiler INSTANTIATES maxValue<int>
maxValue(2.5, 1.5); // ...and maxValue<double>
template<typename T>
class Stack { // class template
std::vector<T> data_;
public:
void push(const T& x) { data_.push_back(x); }
T pop() { T t = data_.back(); data_.pop_back(); return t; }
bool empty() const { return data_.empty(); }
};
Stack<int> si; Stack<std::string> ss; // two distinct generated types
Why it exists. Without templates you'd copy-paste an algorithm per type (an int stack, a string stackβ¦) or erase types via void* and lose type safety (the C way β 01_c_programming.md A3). Templates give one source, many type-safe specializations, zero overhead β the call is resolved at compile time (a form of static polymorphism, D2). They're the foundation of the STL and of compile-time generic, reusable code.
Where you see it (Qualcomm). Generic ring buffers / object pools / fixed-capacity containers reused across audio, camera, and modem; std::vector<uint8_t> for pixel data; type-generic math helpers (clamp<T>). Template-heavy headers are common in performance-sensitive code because everything inlines.
Answer. "A template is a blueprint the compiler uses to generate a type-specialized, fully type-checked function or class for each type you instantiate it with β generic programming. Function templates generalize algorithms over types; class templates generalize containers, like every STL container. It's compile-time polymorphism: one source, many specializations, no run-time cost, full type safety β versus the C approach of void* which erases types and loses checking."
Follow-ups / gotchas. Template definitions usually live in headers (the compiler needs the body to instantiate). Errors can be verbose; C++20 concepts constrain template parameters and clean up diagnostics. Template specialization lets you customize behavior for a specific type. Templates can cause code bloat (one copy per type). Cross-link: static vs dynamic polymorphism β D2.
Seen in: Underpins STL questions (GfG #44 complexity, hash table #12); standard C++ topic.
F2 Β· Q: Compare STL containers β vector, map, unordered_map, set β and their complexities.ΒΆ
Frequency: π₯π₯ Common (~4+ reports) β "Implementation of hash table", "LRU Cache", "difference between Hashing and Hash Tables", complexity analysis recur heavily.
Concept β the basis. The STL provides containers with documented complexities; choosing the right one is the point:
| Container | Underlying structure | Lookup | Insert | Ordered? | Notes |
|---|---|---|---|---|---|
std::vector<T> |
dynamic array | O(n) search, O(1) index | O(1) amortized push_back | by position | contiguous, cache-friendly; random access O(1) |
std::map<K,V> |
self-balancing BST (red-black tree) | O(log n) | O(log n) | sorted by key | ordered iteration; stable worst case |
std::unordered_map<K,V> |
hash table (buckets + chaining) | O(1) average, O(n) worst | O(1) avg, O(n) worst | no | fastest average lookup; degrades with bad hash/collisions |
std::set<T> |
red-black tree | O(log n) | O(log n) | sorted | unique sorted keys; unordered_set = hash version |
Example β picking the container:
std::vector<int> pixels; // sequence, index by position, cache-friendly
std::map<int, Frame> byTimestamp; // need keys in sorted order -> O(log n)
std::unordered_map<int, Frame> byId; // fast id lookup, order doesn't matter -> O(1) avg
std::set<int> seenIds; // unique, sorted membership tests
Why the differences exist. vector is a contiguous array β unbeatable for sequential/indexed access and cache locality, but membership search is linear. map/set use a balanced tree β guaranteed O(log n) and sorted traversal, with no bad-case blowup. unordered_map/unordered_set use a hash table β O(1) average lookup (compute hash β bucket), but a poor hash or adversarial keys cause collisions that degrade to O(n), and there's no ordering. So: need order or worst-case guarantees β tree; need raw average speed and don't care about order β hash.
Where you see it (Qualcomm). unordered_map<id, RequestState> for O(1) per-frame request lookup; vector<uint8_t> for image data; map when you must iterate in timestamp/key order; an LRU cache combines a list (recency order) with an unordered_map (O(1) lookup) β a frequently-asked design (03_dsa.md).
Answer. "vector is a dynamic array: O(1) indexed access and amortized O(1) push_back, but O(n) search β best for sequences and cache locality. map and set are balanced binary search trees, usually red-black: O(log n) operations and keys kept sorted, with no worst-case blowup. unordered_map and unordered_set are hash tables: O(1) average lookup and insert, but O(n) worst case with collisions or a bad hash, and unordered. So I pick unordered_map for fastest average lookup when order doesn't matter, and map when I need sorted iteration or a hard O(log n) guarantee."
Follow-ups / gotchas. vector::push_back is amortized O(1) (occasional reallocation grows capacity geometrically β the standard guarantees only amortized O(1), not a specific factor; libstdc++/libc++ grow Γ2, MSVC Γ1.5; reserve() avoids it); reallocation invalidates iterators/pointers. unordered_map worst case is O(n) β say this; it's the trap. Iterating a map is sorted; iterating an unordered_map is arbitrary. map operations never invalidate other iterators on insert (node-based); unordered_map rehash invalidates iterators (not references). Cross-link: hash table / LRU implementation β 03_dsa.md; tree balancing β 03_dsa.md.
Seen in: GfG #12 ("Implementation of hash table"), GfG #44 ("complexity analysis"), CodingKaro #31 ("LRU Cache"), GfG #24 ("Hashing vs Hash Tables"), GfG Embedded #41 ("which DS is best, complexity").
F3 Β· Q: What are lambdas and function objects (functors)?ΒΆ
Frequency: π₯ Occasional (~aggregator-reported; STL-algorithm usage).
Concept β the basis. A function object / functor is any object with operator() β it's callable like a function but can also carry state. A lambda is concise syntax for an anonymous functor: [capture](params){ body }. The capture list controls how it grabs surrounding variables β [=] by copy, [&] by reference, or named ([x, &y]).
Example:
// functor (stateful callable)
struct Adder { int n; int operator()(int x) const { return x + n; } };
Adder add5{5}; add5(10); // 15
// lambda equivalent + STL algorithm usage
int n = 5;
auto add = [n](int x){ return x + n; }; // captures n by value
add(10); // 15
std::vector<int> v{4,1,3,2};
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; }); // custom comparator
int threshold = 2;
auto cnt = std::count_if(v.begin(), v.end(), [&](int x){ return x > threshold; });
Why they exist. STL algorithms (sort, count_if, for_each, transform) take a callable to customize behavior. Before C++11 you wrote a separate functor class or a free function β verbose and far from the call site. Lambdas let you write the small callable inline, with captured context, making algorithm calls readable and local. A functor/lambda can also be inlined (unlike a function pointer through which the compiler can't see), so it's often faster than a raw function pointer.
Where you see it (Qualcomm). Custom comparators/predicates for sorting frames or filtering requests; per-element transforms over pixel buffers; callbacks/continuations captured with their context; std::function-typed event handlers in C++ frameworks.
Answer. "A functor is an object with operator() β callable like a function but able to hold state. A lambda is anonymous-functor syntax, [capture](params){body}, where the capture list grabs surrounding variables by copy [=], by reference [&], or by name. They're the idiomatic way to pass custom comparators and predicates to STL algorithms like sort and count_if, inline and with captured context β and because the compiler can see the body, it inlines them, often beating a function pointer."
Follow-ups / gotchas. Capturing by reference [&] and outliving the referent dangles β be careful with lambdas stored beyond the captured variables' scope. std::function type-erases any callable (lambda/functor/function pointer) at a small cost. A capture-less lambda converts to a function pointer. mutable lets a by-value-captured lambda modify its copy. Cross-link: function pointers (the C analog) β 01_c_programming.md A4.
Seen in: STL-algorithm usage; standard modern-C++ expectation (sorting/comparators appear across DSA rounds).
F4 Β· Q: What are the four C++ casts, and when do you use each?ΒΆ
Frequency: π₯ Occasional (~aggregator-reported; "cast" probed in code-output and pointer rounds).
Concept β the basis. C++ replaces the blunt C-style (T)x cast with four named casts that say exactly what you mean and let the compiler check:
- static_cast<T> β compile-time, "I know this is safe" conversions: numeric conversions, up/down-casts in a known hierarchy (no run-time check), void*βT*. No run-time verification.
- dynamic_cast<T> β safe down/cross-cast in a polymorphic hierarchy, checked at run time via RTTI: returns nullptr (pointers) or throws std::bad_cast (references) if the object isn't actually that type. Requires a virtual function (it reads the vtable's type info).
- const_cast<T> β add or remove const/volatile. Removing const and then writing a truly-const object is undefined behavior; mainly for interfacing with legacy non-const APIs.
- reinterpret_cast<T> β low-level bit reinterpretation (pointerβinteger, unrelated pointer types). No checks, highly non-portable, undefined behavior if misused β last resort.
Example:
double d = 3.9;
int i = static_cast<int>(d); // 3 (explicit, checked-at-compile-time)
struct Base { virtual ~Base() = default; };
struct Derived : Base { void special(); };
Base* b = getBase();
if (auto* der = dynamic_cast<Derived*>(b)) // RUN-TIME checked; nullptr if not a Derived
der->special();
void legacy(char* s); // old API forgot const
const char* msg = "hi";
legacy(const_cast<char*>(msg)); // OK only if legacy() doesn't write to it
auto addr = reinterpret_cast<uintptr_t>(b); // pointer -> integer (bit-level, portable-ish)
Why they exist. The C cast does any conversion silently, hiding bugs (it can become a reinterpret_cast when you meant a numeric one). The four named casts make intent explicit, are greppable, and let the compiler reject nonsense β static_cast won't compile an unrelated-pointer cast, dynamic_cast actually checks the type, const_cast flags const-stripping. Safer and self-documenting.
Where you see it (Qualcomm). static_cast for numeric/format conversions in pixel math and known up-casts; dynamic_cast to safely probe a concrete node type from a base INode*; reinterpret_cast at the hardware boundary (treating a register address or a byte buffer as a typed pointer β carefully); const_cast only to bridge a legacy C driver API.
Answer. "static_cast is for known-safe compile-time conversions β numerics, up-casts, void* back to a type β with no run-time check. dynamic_cast is a run-time-checked down or cross cast in a polymorphic hierarchy using RTTI; it returns null or throws if the type doesn't match, and needs a virtual function. const_cast adds or removes const/volatile β used to call legacy non-const APIs, and writing through a stripped-const truly-const object is UB. reinterpret_cast is raw bit reinterpretation, no checks, non-portable β a last resort at the hardware boundary. They make intent explicit and let the compiler catch mistakes, unlike a C-style cast."
Follow-ups / gotchas. dynamic_cast has a small run-time cost and needs RTTI enabled (-fno-rtti disables it β common in some embedded builds). static_cast down a hierarchy with the wrong type is UB (no check) β use dynamic_cast when unsure. Prefer the named casts; never C-style casts in C++. Cross-link: C casts / void* β 01_c_programming.md A3; const/volatile β 01_c_programming.md B3.
Seen in: "cast" probing in code-output rounds; standard C++ expectation (follows polymorphism questions in LeetCode C++ #62).
F5 Β· Q: How does C++ exception handling work, and what is exception safety?ΒΆ
Frequency: π₯ Occasional (~aggregator-reported; error-handling rounds).
Concept β the basis. C++ exceptions separate error reporting (throw) from error handling (catch): throw an exception object, the runtime unwinds the stack (destroying local objects along the way β RAII cleanup), until a matching catch in an enclosing try block handles it.
Exception safety levels β guarantees a function gives if an exception propagates:
- No-throw (noexcept): never throws.
- Strong: commit-or-rollback β if it throws, state is unchanged (e.g. copy-and-swap).
- Basic: no leaks, invariants preserved, but state may have changed.
- None: may leak/corrupt β avoid.
Example:
double safeDivide(int a, int b) {
if (b == 0) throw std::invalid_argument("divide by zero");
return double(a) / b;
}
try {
auto r = safeDivide(10, 0);
} catch (const std::invalid_argument& e) { // catch by const reference
std::cerr << e.what() << '\n';
} catch (const std::exception& e) { // base class catches all std exceptions
std::cerr << "other: " << e.what() << '\n';
}
// RAII makes this exception-safe: any lock_guard/unique_ptr local is released during unwind.
Why it exists. Return-code error handling (the C way) is easy to ignore and clutters every call site; exceptions propagate errors automatically to a handler that can deal with them, and combined with RAII they guarantee resources are released during unwinding. The safety levels let you reason precisely about what state a function leaves on failure.
Where you see it (Qualcomm). Mixed: high-level C++ services may use exceptions; but real-time, kernel, and many embedded paths disable exceptions (-fno-exceptions) for determinism and code size, using error codes/std::optional/expected instead. Knowing both β and that destructors must not throw β matters.
Answer. "throw raises an exception object; the runtime unwinds the stack, running destructors of locals along the way, until a matching catch in an enclosing try handles it β catch by const&, ideally deriving from std::exception. Exception safety describes the guarantee on failure: no-throw, strong (commit-or-rollback, like copy-and-swap), basic (no leaks, valid state), or none. RAII is what makes code exception-safe, since locks and memory are released automatically during unwinding. Note many embedded/real-time builds disable exceptions for determinism and size."
Follow-ups / gotchas. Never let a destructor throw β during unwinding a second exception calls std::terminate. Mark non-throwing functions noexcept (enables optimizations and move-on-realloc, B3). Don't throw across a C ABI / library boundary. Catch by reference to avoid slicing the exception object (D6). Cross-link: C error handling / core dumps β 01_c_programming.md; determinism in RTOS β 04_os.md.
Seen in: Error-handling/core-dump rounds (GfG Set 2 #22); standard C++ expectation.
F6 Β· Q: What are namespaces and why use them?ΒΆ
Frequency: π₯ Occasional (~aggregator-reported; assumed knowledge).
Concept β the basis. A namespace is a named scope that groups related declarations and prevents name collisions across libraries/modules. You qualify names with ns::name, or bring them in with using. The standard library lives in std.
Example:
namespace camera {
namespace isp { // nested
int denoise(int x);
}
int denoise(int x); // different from isp::denoise β no clash
}
camera::isp::denoise(5); // fully qualified
namespace ci = camera::isp; // alias
ci::denoise(5);
using camera::denoise; // bring one name in (preferred over whole ns)
Why it exists. In a big system, two libraries may each define init() or Logger. Without namespaces these collide at link time (C's only tool was static/prefix conventions β see 01_c_programming.md B2). Namespaces let each module own its names, so camera::Buffer and gfx::Buffer coexist. They also enable argument-dependent lookup (ADL) so operators are found in the right namespace.
Where you see it (Qualcomm). Vendor/subsystem namespaces (qcamera::, chi::) keep camera, codec, and modem symbols separate; std:: everywhere; namespace aliases shorten deep nesting.
Answer. "A namespace is a named scope that groups related declarations and prevents name clashes between libraries β you access members as ns::name or import them with using. The standard library is in std. It's the modern replacement for C's static/prefix convention for avoiding collisions, and it lets two modules each define a Buffer or init() without conflict. I avoid using namespace std; in headers because it dumps every name into the global scope and can cause ambiguities."
Follow-ups / gotchas. Never put using namespace std; in a header (pollutes every includer). Anonymous namespace namespace { ... } gives internal linkage (the C++ replacement for file-scope static). Namespace aliases tame deep nesting. ADL finds free functions/operators in an argument's namespace β usually helpful, occasionally surprising. Cross-link: linkage / static for file privacy β 01_c_programming.md B1/B2.
Seen in: Assumed across C++ rounds; standard expectation.
Β§ Encyclopedia β searchable glossaryΒΆ
Every bold-italic term used above is defined here, alphabetically, with why it exists / where you see it and a micro-example. Use your editor's find (βF / Ctrl-F) to jump to a term.
Abstract class β a class with at least one pure virtual function; cannot be instantiated, only inherited from / used via a base pointer (D4). Why: defines a contract subclasses must fulfill. struct I { virtual void f()=0; virtual ~I()=default; };
Abstraction β the OOP pillar of exposing what an object does and hiding how (D4, A2). Where: a Codec interface hides whether it's H.264 or HEVC.
Access specifier β public / protected / private: who may name a member (A4). Default: private for class, public for struct.
ADL (argument-dependent lookup) β name lookup that also searches the namespaces of a call's arguments. Why: finds free operators like operator<< in the right namespace (F6).
Aggregate / POD β a simple class/struct with public data and no user-provided constructors; can be brace-initialized. Where: register/packet layouts (use struct, A5).
Constructor β special member that initializes an object (B1). Kinds: default, parameterized, copy constructor, move constructor. Order: bases β members (declaration order) β body.
const-correctness β marking everything const that can be (methods, params, refs) so the compiler enforces read-only intent (C2). Example: int size() const; callable on a const object.
const member function β T f() const; promises not to modify observable state; inside it this is pointer-to-const (C2). Exempt members with mutable.
const_cast β the C++ cast that adds/removes const/volatile (F4). Caveat: removing const and writing a truly-const object is undefined behavior. Where: bridging legacy non-const C APIs.
Copy constructor β T(const T&): makes an independent (deep, for resource types) duplicate (B2/B3). Generated if you don't declare one (and don't declare a destructor/move).
Copy elision / RVO β the compiler omitting a copy/move when returning a local by value. Why: don't std::move a return value β it can defeat RVO (B3).
CRTP (curiously recurring template pattern) β class D : Base<D>; static polymorphism via templates instead of virtuals. Where: zero-overhead "virtual-like" dispatch (D2).
Destructor β ~T(): cleanup when lifetime ends (B1). Make it virtual in a polymorphic base (D5); it must not throw (F5).
Diamond problem β multiple inheritance where two parents share a base, duplicating it in the most-derived class and making members ambiguous (D8). Fix: virtual inheritance.
dynamic_cast β run-time-checked down/cross cast in a polymorphic hierarchy using RTTI; null or bad_cast on mismatch (F4). Needs: a virtual function (reads vtable type info).
Encapsulation β bundling data with the methods that act on it and hiding internals behind private (A1/A2). Why: localize change, protect invariants.
Exception β an object thrown to report an error, handled by a matching catch after stack unwinding (F5). Catch by const&.
Exception safety β the guarantee a function gives on throw: no-throw / strong (commit-or-rollback) / basic / none (F5). RAII provides it.
explicit β keyword blocking implicit conversion via a single-arg constructor (B1). Why: avoid surprising conversions. explicit Buf(size_t);
Friend β a function/class granted access to another class's private/protected members (D9). Not transitive/inherited/symmetric. Where: operator<<, tightly-coupled helpers.
Functor / function object β an object with operator(); callable and stateful (F3). Where: STL comparators/predicates.
Generic programming β writing algorithms/types parameterized over types via templates (F1). Why: one source, many type-safe specializations, zero overhead.
Inheritance β deriving a class to reuse/extend a base; an "is-a" relation (A2/D7). Modes: public (is-a), private/protected (implemented-in-terms-of).
Initializer list (member) β T(...) : a_(x), b_(y) {}: constructs members directly. Gotcha: members init in declaration order, not list order (B1).
Interface β idiom of an abstract class that is all pure virtual with no data β a pure contract (D4). C++ has no keyword; convention.
Lambda β anonymous functor [capture](params){body} (F3). Captures by copy [=], reference [&], or name. Where: inline STL callbacks.
Late binding / dynamic binding β deciding the called function at run time via the vtable (D2/D3). Opposite: early/static binding (compile time).
lvalue / rvalue β an lvalue has a name/address; an rvalue is a temporary. Move semantics bind to rvalues (B3). int& r = x; (lvalue) vs T&& = T{} (rvalue).
Magic statics β C++11 guarantee that a function-local static is initialized exactly once, thread-safely (E3, C3). Where: the Meyers Singleton.
Meyers Singleton β a Singleton implemented as a function-local static returned by reference; thread-safe since C++11 via magic statics (E3). The preferred Singleton.
Move constructor / move assignment β T(T&&) / operator=(T&&): steal the source's resource, leave it empty-but-valid; O(1) (B2/B3). Mark noexcept.
Multiple inheritance β deriving from several bases (D7). Risk: the diamond problem.
mutable β member modifiable even inside a const member function (C2). Where: caches, mutexes. mutable int cache_;
Namespace β a named scope grouping declarations to avoid name clashes (F6). std holds the standard library. Don't using namespace std; in headers.
noexcept β promises a function won't throw (F5/B3). Why: enables optimizations and move-on-realloc; required for vector to move on growth.
Object / instance β a concrete variable of a class type; real storage for its non-static members (A1). Not the C meaning of "any storage" (β 01_c_programming.md).
Object slicing β copying a derived object into a base by value, dropping derived members and reverting virtual dispatch to the base (D6). Fix: use base ref/pointer/smart-pointer.
Operator overloading β defining built-in operators (+, ==, <<, []) for user types (E1). Where: value types, STL iterators, smart pointers.
Overloading β same function name, different parameter lists, resolved at compile time (D1). Compile-time polymorphism.
Overriding β a derived class redefining a base virtual function with the same signature, resolved at run time (D1/D3). Use override.
override / final β override makes the compiler verify you're actually overriding a virtual (catches signature typos); final forbids further overriding/derivation (D1).
Polymorphism β "many forms": one interface, many implementations β compile-time (overloading/templates) or run-time (virtual) (A2/D2).
Pure virtual function β virtual void f() = 0;: a method subclasses must implement; makes the class abstract (D4).
RAII (Resource Acquisition Is Initialization) β tie a resource's lifetime to an object's scope: acquire in ctor, release in dtor, guaranteed even on exceptions (B4). Basis of smart pointers, lock_guard.
Red-black tree β the self-balancing BST typically backing std::map/std::set; guarantees O(log n) (F2). Why: worst-case bound + sorted order.
Reference β an alias for an existing object; bound once, never null, never rebound (C1). int& r = x; Contrast: pointer (nullable, reseatable).
reinterpret_cast β raw bit-level reinterpretation of pointers/integers; no checks, non-portable, UB if misused (F4). Where: hardware boundary, last resort.
RTTI (Run-Time Type Information) β type metadata (stored via the vtable) enabling dynamic_cast and typeid (F4). Disabled by -fno-rtti.
Rule of 0 / 3 / 5 β if you define one of dtor/copy/move you likely need them all (3, then +move = 5); best is to own no raw resources and define none (0) (B2).
rvalue reference (T&&) β binds to temporaries; enables move semantics and perfect forwarding (B3). void f(T&& x);
shared_ptr β smart pointer with shared ownership via an atomic reference count; frees when the last owner dies (E2). Gotcha: cycles leak β use weak_ptr.
Singleton β pattern ensuring exactly one instance with global access (E3). Best form: Meyers Singleton.
Slicing β see object slicing.
Smart pointer β RAII pointer that owns and auto-frees a heap object: unique_ptr / shared_ptr / weak_ptr (E2).
Special member functions β the compiler-managed set: default ctor, destructor, copy ctor, copy assign, move ctor, move assign (governed by Rule of 0/3/5, B2).
static member β data/function belonging to the class, not an object: one shared copy / no this (C3). Where: counters, factories, the Singleton instance.
static_cast β compile-time, known-safe conversion (numeric, up-cast, void*βT*); no run-time check (F4).
Static vs dynamic binding β whether the called function is fixed at compile time (overloading/templates) or chosen at run time (virtual) (D2). Not to be confused with static members (C3).
std::function β type-erased wrapper for any callable (lambda/functor/function pointer) at a small cost (F3).
std::move β a cast to rvalue reference marking an object movable; moves nothing itself (B3). Gotcha: don't apply to a returned local (defeats RVO).
struct vs class β identical in C++ except defaults: struct is public + public-inheritance, class is private + private-inheritance (A5).
Template β a blueprint the compiler instantiates per type β generic, type-safe, zero-overhead (F1). Foundation of the STL.
this β the hidden pointer to the object a non-static member function operates on (A1). const methods make it pointer-to-const.
unique_ptr β smart pointer with exclusive ownership; move-only, zero overhead; the default owning pointer (E2). make_unique<T>().
unordered_map / unordered_set β hash-table containers: O(1) average, O(n) worst-case lookup, unordered (F2). Contrast: tree-based map/set (O(log n), sorted).
vector β dynamic contiguous array: O(1) indexed access, amortized O(1) push_back, O(n) search (F2). Reallocation invalidates iterators.
Virtual destructor β a base destructor marked virtual so delete basePtr destroys the whole derived object; omitting it on polymorphic deletion is undefined behavior (D5).
Virtual function β a member dispatched at run time through the vtable/vptr based on the object's dynamic type (D3). Enables overriding/run-time polymorphism.
Virtual inheritance β class B : virtual A: makes a shared base a single subobject, resolving the diamond problem (D8).
vptr (virtual pointer) β a hidden per-object pointer, set by the constructor, pointing at its class's vtable (D3). Adds one pointer to a polymorphic object's size.
vtable (virtual table) β a per-class array of pointers to that class's virtual function overrides; a virtual call indexes it via the vptr (D3). Also holds RTTI.
weak_ptr β a non-owning observer of a shared_ptr object; doesn't affect the count; lock() yields a shared_ptr or null (E2). Where: break reference cycles.
Β§ Last-5-minutes cheat sheetΒΆ
- Class = type (blueprint); object = instance (storage). Member functions shared; hidden
this. - 4 pillars: Encapsulation (bundle + hide), Abstraction (what not how), Inheritance (is-a, reuse), Polymorphism (one interface, many forms).
structvsclass(C++): identical except defaults βstructpublic + public-inherit,classprivate + private-inherit. (Cstruct= data only.)- Access:
publicanywhere Β·privateclass+friends Β·protected+derived. Defaultprivate(class). - Ctor/dtor order: bases β members (declaration order) β body; destruction reverses it. List members in declaration order.
- Rule of 0/3/5: need one of dtor/copy/move β need all; best is own no raw resource (Rule of 0 via
vector/unique_ptr). - Copy = deep duplicate; move = steal pointer, leave source empty-but-valid (O(1), mark
noexcept).std::move= cast toT&&. - RAII = acquire in ctor, release in dtor; runs on every exit incl. exceptions β no leaks. Basis of smart pointers/locks.
- Reference = alias, never null/rebound; pointer = nullable, reseatable. Pass big objects by
const&. constmethod = won't mutate observable state;mutableexempts a member. Mark everything const that can be.staticmember = per-class (one shared copy / nothis). β static binding.- Overload = same name, diff params, compile-time. Override = redefine virtual, same signature, run-time. Use
override. - Static vs dynamic polymorphism: overloading/templates (compile-time) vs virtual via vtable/vptr (run-time).
- vtable = per-class array of fn pointers; vptr = per-object pointer to it; call = load vptr β index β call.
- Pure virtual
=0β abstract class (can't instantiate) = an interface/contract. - Virtual destructor: required when deleting derived via base pointer, else UB / leak.
- Object slicing: copy derivedβbase by value drops derived part + reverts to base vtable. Use ref/pointer/smart-ptr.
- Diamond problem: shared base duplicated in MI β ambiguous; fix with virtual inheritance (one shared base).
- Friend: grants private access; not transitive/inherited/symmetric. Use for
operator<<, tight helpers. - Smart pointers:
unique_ptr(1 owner, move-only) Β·shared_ptr(refcount, atomic) Β·weak_ptr(observe, breaks cycles). Prefermake_unique/make_shared. - Singleton: Meyers β
static T t; return t;insideinstance()β thread-safe since C++11. DCLP needsatomic+ mutex. - Immutable class: private+
constmembers, onlyconstgetters, deep-copy in, hand out copies β lock-free thread safety. - STL:
vectorO(1) index / O(n) search Β·map/setred-black tree O(log n) sorted Β·unordered_map/sethash O(1) avg, O(n) worst, unordered. - Casts:
static_cast(safe, compile-time) Β·dynamic_cast(RTTI, run-time-checked) Β·const_cast(add/remove const) Β·reinterpret_cast(raw bits, last resort). - Exceptions:
throwβunwind (RAII cleanup)βcatchbyconst&. Safety: no-throw/strong/basic. Destructors must not throw. Often disabled in RT/embedded. - Namespace: scope grouping to avoid clashes; never
using namespace std;in a header.
Evidence base: qualcomm_camera_interview_experiences.md. Frequencies are approximate (sample = 75 reports, some via aggregator pages). Diagrams in assets/ (filenames cpp_*). Cross-references: pure C / pointers / memory map / malloc / endianness β 01_c_programming.md Β· DSA (linked lists, trees, hash-table & LRU implementation, complexities) β 03_dsa.md Β· threads / mutex / semaphore / deadlock / atomics β 04_os.md Β· camera/ISP/multimedia β 05_camera_isp_multimedia.md Β· ML β 06_ml_deeplearning.md Β· kernel / drivers / HAL β 07_embedded_linux_kernel.md Β· puzzles β 08_logical_puzzles_aptitude.md Β· computer arch / digital β 09_computer_arch_digital_design.md Β· LLD / design patterns (Singleton catalog, SOLID, UML) β 10_lld_system_design.md Β· behavioral / projects β 11_behavioral_hr_projects.md.