C++ Language
Modern C++ for interviews and Android native/system code: RAII, the object model, move semantics, templates, STL, and UB.
- C++ is C plus a type system, destructors, and templates. The kernel stays C; Android userspace daemons, HALs and NDK code are C++. Read C first.
- RAII is the central idea: every resource is an object whose destructor releases it. Prefer the Rule of Zero; if you write one of the five special members, write or delete all five.
std::moveis a cast to an rvalue reference, not a move. Compilers already elide many copies; do notmovea return local.unique_ptris the default owner;shared_ptris for shared lifetime and costs an atomic control block;weak_ptrbreaks cycles.- A data race, a dangling reference, iterator invalidation, signed overflow and a strict-aliasing violation are undefined behavior, not "it usually works".
- Android system C++ often builds with
-fno-exceptionsand-fno-rtti, talks Binder via HIDL or AIDL NDK, and meets Java through JNI.
The big picture
C++ is the language of Android native code: SurfaceFlinger, netd, vendor HALs, the NDK, and most performance-critical libraries. Interviews for platform, systems and native roles treat it as a second language after C. You are expected to know the C machine model (bytes, pointers, ABI, UB) and then the C++ object model on top of it (constructors, virtual dispatch, ownership, templates).
This page covers the C++11 through C++20 ideas interviewers actually ask. C++23 additions are marked optional. Algorithm patterns live on Data structures & algorithms; the JVM/ART side is on Java; the kernel itself is C and is covered on Linux kernel & BSP.
C is a workshop with raw tools: you can build anything, and you are responsible for putting every tool back. C++ is the same workshop plus labeled drawers that close themselves (destructors), a parts catalog that stamps out families of tools (templates), and a filing system that knows who owns which drawer (smart pointers). The electricity and floor plan are still C: memory, calling convention, and undefined behavior do not go away.
C machine model (still required)
bytes · pointers · stack/heap · ABI · syscalls · UB
│
▼
C++ object model (what this page adds)
ctors/dtors · vtable · value categories · templates · STL
│
▼
Where it runs on a phone
NDK app ── JNI ── Java/ART see java.html
native daemons (SurfaceFlinger, netd)
HIDL / AIDL C++ HALs ── Binder see binder-aidl.html
Linux kernel (C, not C++) see linux-kernel-bsp.html
Own every resource
RAII and unique_ptr beat manual new/delete. Exception paths and early returns stay safe.
Know the object model
Construction order, vtable dispatch, slicing and this-adjustment are the most common "explain this crash" questions.
Move, do not copy
Heavy objects are moved. std::move is a cast. Return-value optimization already removes many copies.
UB is a real answer
Interviewers want you to say "this is undefined" and name the rule, not "it printed 5 on my machine".
unique_ptr, containers) so cleanup is automatic." Then ask whether they care about userspace Android C++ or language-lawyer questions; the depth differs.C vs C++: what you gain, what you still need
C++ is (almost) a superset of C at the source level, but it is not "C with classes" in practice. You gain a language that can encode ownership, interfaces and generic algorithms. You do not get to forget how a struct is laid out, how a function is called, or what happens when you touch freed memory. Those topics live on the C language page and still appear in every C++ interview.
What C++ adds
- Destructors and RAII: cleanup is tied to scope.
- References, overloading, namespaces,
constas a type qualifier that the compiler enforces. - A real type system: templates,
auto,constexpr, concepts (C++20). - The STL: containers, iterators, algorithms.
- An object model: inheritance, virtual dispatch, access control.
What you still need from C
- Pointers, arrays, pointer arithmetic, and
restrict-style aliasing intuition. - Memory: stack vs heap, alignment, padding, endianness.
- The compilation and linking model, calling conventions,
extern. - Syscalls, file descriptors,
errno, signals (Android native daemons). - C undefined behavior: use-after-free, buffer overflow, signed overflow, data races.
What does not carry over
- The Linux kernel is written in C. Kernel code has no exceptions, no RTTI, no STL, a tiny stack, and its own allocators (
kmalloc). See Linux kernel & BSP. - C++ name mangling means a C++ function is not callable from C unless you wrap it in
extern "C". malloc/freedo not run constructors or destructors. Mixing them withnew/deleteis undefined.- A C struct is standard-layout and trivial if you keep it that way. Adding a virtual function, a user destructor or a non-trivial member changes layout and ABI.
Learning C++ without C is like becoming a chef who only uses a food processor and never learns knife skills. The processor (RAII, STL) is faster and safer for daily work, but when the blade jams you still need to know how cutting works. In interviews, "I would use vector" is good; "and here is why a raw buffer plus realloc would be wrong" is better.
Compilation model: ODR, translation units, ABI
C++ is compiled in translation units (roughly: one .cpp after preprocessing). The compiler never sees the whole program at once unless you use link-time optimization. That is why headers, inline, templates and the One Definition Rule exist.
- Preprocess
#include, macros and#ifproduce a single text stream. - Compile That stream becomes an object file (
.o) with machine code and symbols. - Instantiate templates Each used specialization is compiled in the TUs that need it (unless you explicitly instantiate).
- Link The linker merges object files and shared libraries, resolving symbols and checking the ODR at a coarse level.
- Load The dynamic linker binds shared libraries. ABI mismatches show up here or as silent memory corruption.
The One Definition Rule (ODR)
Every function, variable, class type and template specialization may have only one definition in the whole program, with a narrow exception: an inline function, an inline variable (C++17), or a template may be defined in multiple TUs if every definition is token-for-token the same. Violating the ODR is undefined behavior. The classic bug is a class that looks the same in two TUs but was compiled with different #defines or different language versions.
.cpp files. You get a multiple-definition linker error, or worse, two slightly different inline definitions and an ODR violation the linker cannot see.inline, templates and instantiation
inline no longer means "please put this in the instruction stream". It means "this definition may appear in more than one TU; merge them." Small functions still get inlined by the optimizer whether or not you wrote the keyword. Templates are implicitly inline: the compiler emits a specialization in every TU that uses it, and the linker keeps one copy (COMDAT / weak symbols).
A template is not code until you instantiate it. vector<int> and vector<string> are different types and different machine code. Implicit instantiation happens at the point of use; extern template and explicit instantiation (template class vector<int>;) cut compile time by doing it once.
Precompiled headers (PCH)
A PCH is a serialized compiler state for a stable prefix of includes (often a giant stdafx.h or Android's common headers). It speeds compiles; it does not change the language. If the prefix changes, the PCH is rebuilt. Interviews rarely go deeper than "it caches the parse of expensive headers".
Name mangling, ABI and extern "C"
C++ encodes type information into symbol names so overloads can coexist: _Z3fooii for foo(int, int) in the Itanium ABI that Android and Linux use. C does not mangle (or mangles only with an underscore). extern "C" turns off C++ mangling so a C caller, a JNI entry, or a dlsym can find the symbol.
extern "C" {
JNIEXPORT void JNICALL Java_com_app_Foo_bar(JNIEnv*, jobject);
}
// C++ overloads still mangle:
void bar(int);
void bar(double);
The ABI (application binary interface) is the contract for how types are passed, how vtables are laid out, exception personality routines, and std::string layout. libc++ (Android NDK) and libstdc++ (many desktop Linux distros) are different ABIs; you cannot pass std::string across that boundary. Adding a virtual function, changing a member's type, or flipping -fno-rtti can break a compiled shared library even if the header "still compiles".
_Z + length-prefixed identifiers + type codes. extern "C" skips this and uses the C name.extern "C". Follow-up: "Can I throw a C++ exception through a C callback?" Usually no: the C frame has no unwind info, and Android system code often disables exceptions entirely.Object model: construction, vtables, inheritance
A C++ object is storage plus a lifetime. Construction starts lifetime; destruction ends it. Layout is an ABI decision, but the abstract machine is stable enough that interviews treat it as fact.
Constructor and destructor order
- Bases Virtual bases first (most-derived constructor), then direct non-virtual bases in the order they are listed, not the order in the initializer list.
- Members In the order they are declared in the class, not the initializer-list order.
- Body The constructor body runs last.
- Destruction Exact reverse: body (via the destructor body), then members reverse, then bases reverse, then virtual bases last.
If a member's constructor throws, already-constructed members and bases are destroyed, and the object never existed. That is why RAII members make constructors exception-safe.
Virtual functions and the vtable
A class with a virtual function (or a virtual destructor, or a virtual base) is polymorphic. Typically the object starts with a hidden vptr pointing at a per-class vtable: an array of function pointers, plus RTTI and offset-to-top data for multiple inheritance.
i in the vtable, adjust this by δi (often 0 for single inheritance), then call. Dynamic dispatch is one load plus an indirect call.object of Derived (single inheritance) +-----------+ | vptr | ----> Derived vtable: [dtor | foo | bar | RTTI] | Base data | | Der data | +-----------+
- Virtual Late binding through the vtable. The function you wrote in the most-derived class that overrode that slot runs.
- Pure virtual
= 0. The class is abstract; you cannot construct it. A pure virtual can still have a definition (useful for a pure virtual destructor). - Override Write
override(C++11). If the base signature does not match, the program does not compile. This catchesconstand ref-qualifier mistakes. - final On a class or virtual function: no further override. Helps the compiler devirtualize.
Slicing
Assigning or passing a Derived by value as a Base copies only the base subobject. Virtual behavior is gone; derived members are gone. That is slicing. Polymorphic types are passed by pointer or reference (and usually owned by unique_ptr<Base>).
Multiple inheritance, virtual inheritance, this-adjustment
With two non-virtual bases, the object contains both, one after the other. A pointer to the second base is not the same address as the complete object. The compiler inserts a this-adjustment when you convert Derived* to Base2* or when a virtual call is made through Base2.
class Derived : public Base1, public Base2 +------------------+ | Base1 (vptr1) | <-- Derived* and Base1* point here | Base2 (vptr2) | <-- Base2* = Derived* + sizeof(Base1') | Derived fields | +------------------+
The diamond problem: D inherits B and C, both inheriting A. Without virtual inheritance you get two A subobjects. With virtual inheritance there is one shared A, found through a vbase offset (often a vbptr). Virtual inheritance is rare in Android code; if you need interfaces, prefer a single abstract base or composition.
Empty base and sizeof
An empty class still has sizeof == 1 so that two objects have distinct addresses. An empty base may occupy no bytes: the empty base optimization (EBO). That is why unique_ptr<T, Deleter> is often pointer-sized when Deleter is empty. C++20 [[no_unique_address]] gives the same idea for members.
Base* pointing at Derived, then a virtual call. Say "load vptr, index the slot, possibly add a this delta, indirect call." Follow-up is almost always slicing or "what happens if the destructor is not virtual and I delete through Base*?" (undefined: only ~Base runs).Value categories, move semantics and elision
Every C++ expression has a type and a value category. Interviews expect the C++11 taxonomy, not "left of = versus right".
| Category | Meaning | Example |
|---|---|---|
| lvalue | Has identity; you can take its address. Not "about to die". | a variable, *p, a function returning T& |
| xvalue | eXpiring: identity, but the resources may be reused. "About to die". | std::move(x), a.b if a is an rvalue |
| prvalue | Pure rvalue: a temporary in the abstract machine, or a literal. | 42, T{}, a function returning T |
| glvalue | lvalue or xvalue (has identity). | |
| rvalue | xvalue or prvalue (can bind to T&&). |
An lvalue is a house you still live in. An xvalue is a house you have already sold: the address exists, but the furniture can be taken. A prvalue is a house being built that may be constructed directly on the lot (elision) instead of built elsewhere and moved. std::move is the "for sale" sign: it does not move the furniture; it only changes how the house is categorized so a move constructor is allowed to strip it.
std::move is a cast
template<class T>
constexpr remove_reference_t<T>&& move(T&& t) noexcept {
return static_cast<remove_reference_t<T>&&>(t);
}
It does not move. The move constructor or move assignment operator, if selected by overload resolution, does the steal. If the type has no move members, a copy is used. If the object is const, you get const T&&, which cannot bind to T&&, so you copy. Moving from an object leaves it in a valid but unspecified state (for STL types: empty-ish, still destructible and assignable).
Copy vs move
| Copy | Move | |
|---|---|---|
| Signature | T(const T&) | T(T&&) |
| Source | Unchanged | Emptied / transferred |
| Cost | Deep copy (or refcount bump) | Pointer steal, O(1) for many types |
| When | You need two independent objects | You are done with the source |
Return value optimization and copy elision
C++17 guarantees elision for a prvalue initializing an object of the same type: T x = factory(); constructs x directly. The compiler may still perform NRVO (named return value optimization) when you return local;, but that is not guaranteed. return std::move(local); blocks NRVO and is a pessimization. Return the local by name.
std::move as "I am finished with this name." Do not move from a function argument you still use, from *this unless you mean to, or from a const object. Do not move a returned local.Rule of Zero, Three and Five
A class has five special members: destructor, copy constructor, copy assignment, move constructor, move assignment. The compiler generates what it can from members. Your job is to stay out of the way, or to be complete.
Rule of Zero
If every resource is already managed by a member (container, smart pointer, std::string), write none of the five. This is the default for new code.
Rule of Three
If you write a destructor, copy constructor or copy assignment because you own a raw resource, you need all three (C++98). Otherwise a copy double-frees.
Rule of Five
In C++11, writing any of the three suppresses implicit moves. Write or = delete the two move members as well. All five, or none.
When you must write all five
- You own a raw pointer, fd, lock, or
mmapregion and you did not wrap it in an existing RAII type. - You need a deep copy (a cloneable handle) or you must forbid copying (
= deletethe copy pair, default or write the move pair). - You maintain an invariant that the compiler-generated member-wise copy would break (an internal pointer into your own buffer).
Prefer wrapping the resource and returning to the Rule of Zero. A custom destructor plus compiler-generated copies is the classic leak/double-free interview bug.
swap and copy-and-swap
A non-throwing swap that exchanges two objects' guts is the building block of strong exception-safe assignment:
T& T::operator=(T other) { // pass by value: copy or move in
swap(*this, other);
return *this;
} // other's destructor frees the old guts
If the copy (the parameter) throws, *this is unchanged. Provide a friend swap that is noexcept and ADL-findable so STL algorithms and containers can use it.
std::string.RAII and smart pointers
RAII (Resource Acquisition Is Initialization) means the constructor acquires a resource and the destructor releases it. Scope exit, exception unwind, and early return all run destructors. File descriptors, locks, mappings and heap objects should be RAII, not manual pairs of calls.
A hotel key card that stops working when you check out: you do not call the front desk to lock the room if you leave through any door, including the fire exit. The destructor is check-out. In code, lock_guard unlocks on every path; unique_ptr deletes on every path.
| Type | Ownership | Copy | When |
|---|---|---|---|
unique_ptr<T> | Exclusive | No (move only) | Default. Factory returns, PIMPL, polymorphism. |
shared_ptr<T> | Shared, atomic refcount | Yes | Shared lifetime you cannot stack-scope. |
weak_ptr<T> | Non-owning observer | Yes | Break cycles; caches; "is it still alive?" |
raw T* | None (or ambiguous) | Yes | Non-owning view, or C APIs. Document it. |
Control block and make_shared
strong drops to 0. Control block is freed when strong == 0 and weak == 0. weak_ptr does not keep the object, only the block.make_shared<T>(args) typically allocates the object and the control block in one heap allocation. shared_ptr<T>(new T) is two allocations and a brief window where a throw leaks (unless you are careful). The combined allocation means the object storage may stay alive until the last weak_ptr dies, which matters for large objects.
enable_shared_from_this
If an object already owned by a shared_ptr needs to hand out more shared_ptrs to itself (callbacks, Binder death recipients, async work), inherit enable_shared_from_this<T> and call shared_from_this(). Calling it when no shared_ptr owns the object throws bad_weak_ptr (or is UB in older wording). Never shared_ptr<T>(this): that creates a second control block and a double-free.
Custom deleters
unique_ptr<FILE, int(*)(FILE*)> f(fopen("a", "r"), &fclose);
unique_ptr<int, decltype(&std::free)> p(
static_cast<int*>(std::malloc(n)), &std::free);
Android uses this for close on fds, ANativeWindow_release, and HIDL/sp<>-style handles. A function-pointer deleter makes unique_ptr two words; an empty function-object deleter can be EBO'd down to one.
Why auto_ptr died
auto_ptr (C++98) transferred ownership on copy. Passing it to a function emptied the source silently. It could not live in standard containers. C++11 replaced it with unique_ptr (move-only, correct) and removed auto_ptr in C++17. If you see it in old code, treat it as a bug farm.
shared_ptrs that point at each other (or a parent/child pair both owning) never hit a strong count of zero. Use weak_ptr on one edge. Android listener lists often store weak_ptr or wp<> for this reason.Memory: new, allocators, placement new, alignment
new T allocates storage (usually operator new → malloc) and then constructs. delete p destroys and then deallocates. new T[n] / delete[] p must be paired; mixing with scalar delete is UB. malloc does not construct; free does not destroy.
| API | Constructs? | Notes |
|---|---|---|
new / delete | Yes | Exceptions: new throws bad_alloc unless nothrow. |
malloc / free | No | C ABI. Use only for C interop or raw bytes. |
allocator / allocator_traits | Via construct | What containers actually call. |
placement new | Yes, in existing storage | No allocation. You must destroy manually. |
Placement new
alignas(T) unsigned char buf[sizeof(T)];
T* p = new (buf) T(args); // construct in buf
p->~T(); // destroy; do not delete p
Containers, optional, and variant do this. After destroying, you may construct a new object in the same bytes. Pointers to the old object are stale; C++17 std::launder(p) is the light-touch way to say "treat this pointer as pointing at the new object" when the compiler could assume the old object's const or reference members were immutable.
Alignment
alignas(16) / alignof(T) matter for SIMD, atomics on some ABIs, and over-aligned types. operator new in C++17 honours new-extended alignment. A buffer on the stack that is not aligned for T makes placement new UB. Android NDK code that shares buffers with the CPU and a device should also think in cache-line (64-byte) alignment.
Allocators (light)
A container's allocator is a handle that can allocate / deallocate raw memory. allocator_traits<A> fills in defaults so you can write a minimal allocator. You rarely write one in an interview; you should know that vector<T, A> is a different type from vector<T>, that std::pmr:: (C++17) adds a runtime polymorphic memory resource, and that new is not what vector calls for each element (it allocates a buffer, then placement-news elements).
new. If you must allocate, make_unique / make_shared. If you must use placement new, pair it with an explicit destructor call and aligned storage.Templates: deduction, specialization, SFINAE, concepts
A template is a blueprint. The compiler generates a concrete function or class when you use it. That is how the STL is generic without virtual dispatch, and why template errors are long: the compiler is showing you the instantiation stack.
Deduction
Function templates deduce T from arguments. T&& in a template is a forwarding reference (not "rvalue reference only"): it becomes U& for lvalues and U&& for rvalues. Use std::forward<T>(x) to restore the category. Class templates needed explicit arguments until C++17 CTAD (pair p{1, 2.0};).
Specialization vs overload
- Function overloads are picked by overload resolution. Prefer extra overloads over specializing function templates.
- Class full specialization
template<> class Foo<int> { ... };replaces the primary template for that type. - Partial specialization (classes only)
template<class T> class Foo<T*>matches a subset. - A function template can be fully specialized but it interacts poorly with overloads; interviews treat "don't specialize functions" as the safe rule.
SFINAE vs concepts (C++20)
SFINAE ("Substitution Failure Is Not An Error"): if substituting a template argument into a function template's signature fails, that candidate is dropped, not a hard error. Classic tools: enable_if, void_t, trailing decltype. It is how pre-C++20 STL constrained overloads.
Concepts name requirements: template<std::integral T> or requires std::movable<T>. Failed constraints produce readable errors and participate in overload ranking. Prefer concepts in C++20 code; still recognize SFINAE in older Android code and interview whiteboards.
CRTP
template<class D>
struct Base {
void iface() { static_cast<D*>(this)->impl(); }
};
struct Widget : Base<Widget> {
void impl() { /* ... */ }
};
The Curiously Recurring Template Pattern gives static polymorphism: no vtable, inlinable, but each D is a different type. Used in expression templates and some Android helpers. Contrast with a virtual interface when you need a heterogeneous container of bases.
Variadic templates
A parameter pack class... Ts expands with Ts.... C++17 fold expressions ((os << ... << args)) replace most recursive pack functions. Perfect forwarding of packs is std::forward<Ts>(args).... This is how make_unique, tuples and format-style APIs work.
make_unique or a type trait. Talk through deduction, new T(std::forward<Args>(args)...), and why the function is a template. If they say "constrain this to integers", use a concept or enable_if_t<is_integral_v<T>>.STL: containers, iterators, algorithms, ranges
The Standard Template Library is containers plus iterators plus algorithms. Prefer an algorithm with a name over a raw loop: the name states intent and the implementation is already correct about edges. Complexity and invalidation rules are interview staples; see also DSA for problem-solving patterns.
Container complexity (typical)
| Container | Random access | Insert / erase middle | Notes |
|---|---|---|---|
vector | O(1) | O(n) | Contiguous. Default sequence. Amortized O(1) push_back. |
deque | O(1) | O(n) middle; O(1) ends | Chunked. References stable on end insert, not iterators in all impls. |
list / forward_list | No | O(1) given iterator | Node-based. Poor cache. Rarely the right default. |
map / set | No | O(log n) | Ordered, typically red-black tree. Stable iterators (except erased). |
unordered_map / set | No | Avg O(1), worst O(n) | Hash table. Rehash invalidates iterators. |
push_back is O(1) because the geometric series of copies sums to O(n).Iterator invalidation (remember these)
vector: any insert that exceeds capacity reallocates and invalidates all pointers, references and iterators. Erase atiinvalidates fromito end.deque: insert/erase in the middle invalidates all; at ends, references often stay valid, iterators may not.list: only iterators to erased elements die.map/set: only the erased element's iterator dies. Pointers to other nodes stay valid.unordered_map: insert that rehashes invalidates all iterators (pointers to elements stay valid until that element is erased, in the standard).
unordered_map vs map
map
- Ordered by
<(or a comparator). - O(log n) find, no hash needed.
- Iterator walks in key order.
- No rehash surprises.
unordered_map
- Average O(1) find if the hash is good.
- Needs
std::hash<Key>and equality. - Worst case O(n) (adversarial keys, or a broken hash).
- Rehash can spike latency; reserve if you know the size.
String SSO
Small string optimization: a std::string stores a short string inside the object (typically 15 bytes on 64-bit libc++, sometimes 23) and only heap-allocates when it grows past that. Moving a small string may still copy those bytes; moving a large string steals the heap pointer. Do not take a char* into a string and then mutate the string.
Algorithms and ranges (light)
Algorithms take iterator pairs: sort, find_if, lower_bound, copy_if, accumulate. They assume the iterator category they need (random access for sort). C++20 ranges add lazily composed views (filter, transform) that do not allocate, and algorithms that take a range instead of two iterators. Know they exist and that a view does not own data; interviews rarely ask you to implement a view.
data() pointer across a push_back that reallocates. The crash is often "later, in another function", which is why interviewers love it.Const, constexpr, consteval, mutable
const is a contract the compiler helps you keep. constexpr is "this can run at compile time". They are related but not the same.
| Keyword | When | Notes |
|---|---|---|
const | Runtime or compile time | Does not mutate through this access path. Can still mutate mutable members or via another non-const path. |
constexpr | C++11 functions; relaxed each standard | May run at compile time if arguments allow; may also run at runtime. |
consteval | C++20 | Must run at compile time (immediate function). Light interview topic. |
constinit | C++20 | This variable is statically initialized (no dynamic init / "static init order fiasco"). Light. |
const T*is a pointer to const;T* constis a const pointer. Read from right to left.- A const member function cannot call a non-const one or mutate non-
mutablemembers. It is part of the vtable slot:foo()andfoo() constare different. mutableexists for logical const: a mutex inside a const method, or a cached hash. Do not use it to lie about mutating observable state without synchronization.
const_cast dangers
const_cast can strip const or volatile. If the object was originally defined as const (or lives in read-only memory), writing through the cast is undefined behavior. The legitimate uses are interfacing with a C API that forgot const, and rarely calling a non-const overload when you have proven the object is mutable. Prefer overloads and span<const T> instead.
const for the compiler or for humans?" Both: it documents intent, enables overloads, and lets the compiler assume that a const object (the original one) does not change. Then they ask about const_cast and you mention UB on a truly const object.Exceptions, noexcept and why Android often disables them
A thrown exception unwinds the stack, running destructors of automatic objects. That is why RAII and exceptions fit together: you do not write catch just to free memory. You catch at a boundary that can decide (retry, log, convert to an error code).
Exception safety levels
| Level | Guarantee |
|---|---|
| No throw | The operation never throws (noexcept, or it truly cannot). |
| Strong | Succeeds completely or leaves state unchanged (copy-and-swap, transactional). |
| Basic | No leaks; invariants hold; state may be changed (the usual STL container guarantee except where documented). |
| None | A throw may leak or corrupt. Unacceptable in modern C++. |
noexcept is both a contract and an optimization. vector will move elements on reallocation only if the move constructor is noexcept; otherwise it copies, so a throw mid-reallocation can still give the strong guarantee. Mark moves noexcept when they truly cannot throw.
Why Android / system code uses -fno-exceptions
- Binary size and unwind tables cost RAM and flash on phones.
- A thrown exception through a JNI boundary, a C callback, or a Binder thread is a process killer if it is not caught.
- Code review prefers explicit
status_t,ndk::ScopedAStatus, orstd::optional/ error codes at IPC edges. - The same flag often comes with
-fno-rttito save more size. Thendynamic_castandtypeidare gone.
App NDK code may enable exceptions; many system daemons and HALs do not. Know which world you are in. Destructors must not throw in either world: during unwind, a second exception calls terminate.
noexcept function that throws (calls terminate). Never throw from a destructor. Never assume new cannot fail if you actually run with exceptions enabled.Concurrency: threads, mutexes, atomics, memory order
C++11 put a memory model and a thread library in the standard. The one rule that matters most: a data race is undefined behavior. Two threads accessing the same non-atomic object, at least one writing, without synchronization, is a data race. "It works on ARM until it does not" is not an answer. OS-level scheduling is on Linux kernel & BSP; this section is the language rules.
| Tool | Role |
|---|---|
std::thread | Starts a thread. Join or detach before the thread object dies, or std::terminate. |
std::mutex | Exclusive lock. Not recursive unless recursive_mutex. |
lock_guard | RAII lock; unlocks on scope exit. No unlock / defer. |
unique_lock | RAII lock that can unlock, defer, or work with condition_variable. |
std::atomic<T> | No data race on that object. Choose a memory_order. |
condition_variable | Wait for a predicate. Always wait in a loop; spurious wakeups happen. |
async / future | Run work and retrieve a result. async launch policy is tricky; many codebases use a thread pool instead. |
Memory order (what interviews want)
relaxed: atomicity only. No happens-before. Counters, stats.acquire(load) /release(store): the release's prior writes become visible to the acquire. The usual "publish a pointer" pair.acq_rel: read-modify-write that both acquires and releases.seq_cst: default. A single total order of all seq_cst ops. Easiest to reason about; slightly heavier on some CPUs.
A mutex lock is an acquire; unlock is a release. You do not need atomics for data that is always accessed under that mutex.
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&]{ return ready; }); // unlocks while waiting
// predicate true, lock held
std::function or a lambda that captured this after the object died. Forgetting to join a std::thread.int. The answer is "data race, UB", not "one wins". Then they ask how to fix it: mutex, or atomic, and what the memory order means. On Android they may also ask about Binder thread-pool exhaustion (see Binder & AIDL), which is a systems problem sitting on top of these primitives.Undefined behavior in C++
Undefined behavior means the standard imposes no requirements. The compiler may delete the code, the program may appear to work, or it may wipe the stack three frames later. Treat "I ran it and it was fine" as not evidence.
Dangling references
Returned local, invalidated iterator, pointer into a moved-from string buffer, or a reference bound to a temporary that already died.
Iterator invalidation
Using a vector iterator after push_back reallocated. See the STL section.
Signed overflow
int overflow is UB. Unsigned wrap is defined (mod 2n). Compilers assume signed overflow never happens and will optimize on that.
Strict aliasing
Accessing an object through a glvalue of the wrong type (except char / byte and a few cases). memcpy or bit_cast (C++20) instead of type-punning through a pointer cast.
Data races
Concurrent conflicting access to a non-atomic. Not a "race you can live with".
Use after move
Not automatically UB, but the object is unspecified. Calling a method that assumes invariants (a non-empty string, a non-null unique_ptr) is a bug and can become UB.
Other classics: use after free, double free, buffer overflow, mismatched new/delete[], calling a virtual on a destroyed object, shifting a signed integer into the sign bit (rules tightened over standards; do not do it), and lifetime errors with placement new without launder when required.
UB is a contract with a demolition crew: you promised not to stand in a marked zone. If you do, they may demolish the building, ignore you, or demolish a different building next week. The compiler is the crew: once you step on a dangling pointer, later "safe" code may be optimized as if that path could not happen.
Lambdas, captures, std::function and bind
A lambda is a compiler-generated function object (a class with operator()). Closures capture names from the enclosing scope.
| Capture | Meaning |
|---|---|
[] | Nothing. Convertible to a function pointer if it does not capture. |
[=] | Copy used automatic variables (not *this by default until deprecated patterns; prefer explicit). |
[&] | Reference all. Dangerous if the lambda outlives the scope (posted work, threads, STL stored predicates). |
[this] | Copy the pointer this. The object must outlive the lambda. |
[*=this] (C++17) | Copy the object. Safer for fire-and-forget if the object is small and copyable. |
[x, &y] | Explicit: copy x, reference y. Preferred style. |
mutable on a lambda lets operator() modify captured-by-value members. Generic lambdas (auto parameters, C++14) are function templates in disguise. C++20 adds template parameter lists on lambdas and constexpr lambdas more freely.
std::function cost
std::function<void(int)> is type erasure: it can hold a lambda, a function pointer, or a bind-expression. That flexibility costs: a possible heap allocation (unless small-buffer optimization applies), an indirect call, and copies that may allocate again. In hot paths (SurfaceFlinger per-frame, audio callbacks) prefer a template parameter, a function pointer, or a concrete function object. Do not put std::function in a per-pixel loop.
std::bind
bind exists; lambdas replaced almost every use. bind is harder to read, has nested-bind pitfalls, and copies arguments eagerly. Prefer a lambda that calls the function with the right arguments. Know bind only so you can read old code.
[&] stored on a Binder thread or a std::thread after the callee returned. The captures dangle. Capture what you need by value, or keep the object alive with shared_ptr / shared_from_this.C++ on Android: NDK, HALs, daemons, JNI
On a phone, C++ lives in three bands: app NDK libraries, privileged native daemons and framework native code, and vendor HAL processes. The kernel below them is C. Java/Kotlin above them meets C++ through JNI. Details of services sit on Android frameworks; the IPC is on Binder & AIDL; the language under the HAL is still this page plus C.
App process (ART)
Java / Kotlin <-- JNI --> C++ NDK (.so, libc++)
│ Binder (AIDL)
▼
system_server (mostly Java) native daemons (C++)
SurfaceFlinger · netd · installd · lmkd
│ Binder (stable AIDL or HIDL)
▼
Vendor HAL process (C++ NDK or HIDL C++)
│ ioctl / mmap / Binder
▼
Linux kernel (C)
NDK and libc++
The NDK compiler is Clang. The C++ standard library is libc++ (LLVM), not libstdc++. The STL is linked either statically (c++_static, larger .so, no version dance) or shared (c++_shared, one libc++.so, must be packaged). You cannot reliably pass std::string or std::vector across a library compiled with a different STL or a different _LIBCPP_ABI. Keep ABI-stable boundaries in C types, AIDL parcelables, or NDK ABinder types.
HIDL vs AIDL C++
| HIDL C++ | AIDL NDK C++ | |
|---|---|---|
| Transport | /dev/hwbinder, hwservicemanager | /dev/binder (or vndbinder), servicemanager |
| Types | hidl_string, hidl_vec, sp<IFoo> | std::string, std::vector, ndk::SpAIBinder |
| Errors | Return status / HIDL return | ndk::ScopedAStatus |
| Vendor rule | Legacy; no new HIDL HALs | Required backend for vendor (libbinder_ndk) |
Platform-internal C++ can still use the CPP binder backend (android::sp, Binder). Vendor and APEX code must use the NDK backend. Full IPC mechanics: Binder & AIDL.
System daemons
- SurfaceFlinger Compositor. C++, performance-critical, vsync, Binder from WMS and apps, talks HWC HAL. Not inside
system_server. - netd Networking daemon: iptables/nft, tethering, network observers. C++, Binder to the framework.
- installd, lmkd, logd, mediaserver / codec services: privileged C++ processes started by init.
These processes often compile without exceptions, use Android's sp<> / wp<> refcounting (historically) alongside unique_ptr, and dump state through dumpsys. Lifetime bugs here reboot a subsystem or the device, not just an app.
JNI with C++
extern "C" JNIEXPORT jint JNICALL
Java_com_app_Foo_add(JNIEnv* env, jobject /*thiz*/, jint a, jint b) {
return a + b;
}
- JNI entry points are
extern "C"so the VM candlsymthem, or you register them withRegisterNativesinJNI_OnLoad. - C++ exceptions must not escape into the VM. Catch at the boundary and raise a Java exception with
ThrowNew, or abort. - Local refs are freed when the native method returns; in a long C++ loop creating objects, call
DeleteLocalRefor a frame. Global refs needDeleteGlobalRef. - Own Java heap objects with a small RAII wrapper around
NewGlobalRef/DeleteGlobalRef. Own native objects that Java holds with ajlongpointer plus anativeFinalizeorCleaner.
The Java side of the same boundary is on Java.
Interview coding: RAII, unique_ptr, dispatch, containers
Whiteboard rounds for C++ roles mix language questions with a short implementation. They are not asking you to reimplement the STL; they are asking whether you default to ownership in types and whether you know when a container is wrong.
RAII wrapper (file descriptor)
class UniqueFd {
int fd_ = -1;
public:
explicit UniqueFd(int fd) : fd_(fd) {}
~UniqueFd() { if (fd_ >= 0) ::close(fd_); }
UniqueFd(const UniqueFd&) = delete;
UniqueFd& operator=(const UniqueFd&) = delete;
UniqueFd(UniqueFd&& o) noexcept : fd_(o.fd_) { o.fd_ = -1; }
UniqueFd& operator=(UniqueFd&& o) noexcept {
if (this != &o) { reset(); fd_ = o.fd_; o.fd_ = -1; }
return *this;
}
int get() const { return fd_; }
int release() { int x = fd_; fd_ = -1; return x; }
void reset() { if (fd_ >= 0) { ::close(fd_); fd_ = -1; } }
};
unique_ptr sketch (what they want to hear)
- Store
T*and a deleter. Default deleter callsdelete. - Delete the copy pair. Implement move: steal pointer, null the source.
resetdeletes the old pointer then takes the new one.releasegives up ownership without deleting.operator*/operator->/get.boolconversion.- Array specialization uses
delete[]. - Say you would use
std::unique_ptrin production.
Virtual dispatch questions
deletethroughBase*without a virtual destructor: UB.- Virtual call in constructor: not the derived override.
- Slicing:
vector<Base>cannot hold aDerived. - Use
vector<unique_ptr<Base>>for a heterogeneous list.
Which container?
| Need | Pick |
|---|---|
| Default list of T, index, append | vector |
| Queue both ends, not node-based | deque (or vector + index) |
| Stable node pointers, splice | list (justify cache cost) |
| Ordered keys, range queries | map / set |
| Average O(1) lookup, no order | unordered_map, reserve |
| Non-owning contiguous view | span (C++20), not a container |
| Fixed compile-time size | array |
const, avoided naked new, and did not return a reference to a local.Quick revision
- C++ adds RAII, a stricter type system and templates on top of the C machine model; the Linux kernel stays C.
- A translation unit is one
.cppafter preprocessing. The ODR forbids two different definitions of the same entity. inlinemeans "this definition may appear in many TUs", not "please inline this call".- Templates instantiate per used specialization; the linker keeps one copy (COMDAT).
- Name mangling encodes types;
extern "C"disables it for C, JNI anddlsym. - ABI is layout + calling convention + vtable + standard-library object layout. libc++ and libstdc++ are incompatible.
- Bases construct first, then members in declaration order, then the body; destruction is the reverse.
- Virtual call: load
vptr, index the slot, optionally adjustthis, indirect call. - Virtual calls in constructors and destructors do not reach the most-derived override.
- Deleting through
Base*without a virtual destructor is undefined behavior. - Slicing: passing a derived object by base value drops derived data and virtual behavior.
- Multiple inheritance may require a
this-adjustment; virtual inheritance shares one base in a diamond. - Empty class
sizeofis 1; empty bases can be 0 bytes (EBO). That is why a defaultunique_ptris pointer-sized. - lvalue = identity, still alive; xvalue = identity, expiring; prvalue = temporary / initializer.
std::moveis a cast toT&&. It does not move; the move constructor might.- Moving a
constobject usually copies. Do notreturn std::move(local). - C++17 guarantees elision for prvalues of the same type; NRVO is optional.
- Rule of Zero: manage resources with members. Rule of Five: if you write one special member, write or delete all five.
- Copy-and-swap gives the strong exception guarantee for assignment if swap is non-throwing.
- RAII ties release to destruction so every exit path cleans up, including exceptions.
unique_ptrexclusive;shared_ptrshared + atomic control block;weak_ptrobserves and breaks cycles.make_sharedis one allocation (object + control block). The object storage may outlive the object while weak refs remain.- Never
shared_ptr<T>(this); useenable_shared_from_thisonly when ashared_ptralready owns the object. auto_ptrtransferred on copy and was removed; useunique_ptr.newconstructs;mallocdoes not. Pairnew[]withdelete[]. Placement new needs an explicit destructor.alignas/alignofmatter for SIMD and over-aligned types.std::launderafter reusing storage (light).- A template
T&¶meter is a forwarding reference; usestd::forward. - Prefer overloading functions to specializing them. Specialize class templates; use partial specialization for families of types.
- SFINAE drops bad candidates; C++20 concepts name the same constraints with better errors.
- CRTP is static polymorphism (no vtable). Variadic packs expand with
...; C++17 adds folds. vectoris the default sequence: contiguous, amortized O(1) append, geometric growth (~2× on libc++).vectorreallocation invalidates all iterators, pointers and references.mapis O(log n) ordered;unordered_mapis average O(1), worst O(n), rehash invalidates iterators.- SSO keeps short
std::stringinline (often 15 bytes on 64-bit libc++). - C++20 ranges are lazy non-owning views plus range algorithms; a view does not own data.
constis a type qualifier;constexprmay run at compile time;constevalmust;constinitis static init (C++20, light).- Writing through
const_castto an object that was bornconstis UB. - Exception safety: no-throw, strong, basic, none. Destructors must not throw.
vectormoves on reallocation only if the move isnoexcept; otherwise it copies.- Android system C++ often uses
-fno-exceptionsand-fno-rttifor size and JNI/Binder safety. - A data race (conflicting non-atomic access, at least one write, no sync) is UB.
lock_guardis simple RAII;unique_lockworks withcondition_variable. Always wait with a predicate loop.memory_order: relaxed = atomic only; acquire/release = publish; seq_cst = default total order.- Signed overflow, strict aliasing violations, dangling refs and iterator invalidation are UB.
- Use-after-move is a valid but unspecified state for STL types; treating it as fully intact is a bug.
- Prefer explicit lambda captures.
[&]stored past the scope dangles.std::functiontype-erases and may heap-allocate. - Android NDK uses libc++. Do not pass STL types across mixed-STL or mixed-ABI
.soboundaries. - HIDL C++ is legacy hwbinder; new vendor HALs are AIDL NDK (
libbinder_ndk,ScopedAStatus). - SurfaceFlinger and netd are native C++ daemons; JNI is
extern "C"and must not leak C++ exceptions into ART. - Default coding answers: RAII wrappers,
unique_ptr,vector, no nakednew, no returned locals, virtual dtor on bases.
Glossary
- ABI
- Application binary interface: how types, calls, vtables and library objects are laid out so separately compiled files can link.
- ADL
- Argument-dependent lookup (Koenig lookup): unqualified functions are also searched in the namespaces of the argument types, which is how
swapand operators are found. - AIDL NDK
- C++ backend for AIDL that links
libbinder_ndk; required for vendor and APEX HAL code. - alignas
- Attribute that requests a minimum alignment for a variable, member or type.
- allocator_traits
- Traits class that gives containers a uniform way to allocate, construct and destroy through any allocator.
- auto_ptr
- Deprecated C++98 smart pointer that transferred ownership on copy; removed in C++17 in favor of
unique_ptr. - concept
- C++20 named set of requirements on a template argument; failed concepts drop or rank overloads with readable errors.
- consteval
- C++20: the function must be evaluated at compile time (immediate function).
- constexpr
- May be evaluated at compile time when arguments allow; the same function can still run at runtime.
- constinit
- C++20: this variable must have static initialization, avoiding the dynamic initialization order fiasco.
- control block
- Heap object beside a
shared_ptrthat stores strong and weak counts, the deleter and the allocator. - copy elision
- Skipping a copy or move by constructing the target in place; guaranteed for many prvalues since C++17.
- copy-and-swap
- Assignment implemented as construct-a-temporary then
swap, giving the strong exception guarantee. - CRTP
- Curiously Recurring Template Pattern:
DerivedinheritsBase<Derived>for static polymorphism without a vtable. - CTAD
- Class template argument deduction (C++17): the compiler infers class template parameters from a constructor call.
- data race
- Conflicting concurrent accesses to a non-atomic object, at least one a write, without happens-before; undefined behavior.
- deleter
- Callable stored in a smart pointer that releases the resource (default:
delete). - EBO
- Empty base optimization: an empty base subobject may take no storage, unlike an empty standalone object.
- enable_shared_from_this
- Mixin that lets an already shared object produce more
shared_ptrs to itself via a weak back-pointer. - exception safety
- What state remains if a throw occurs: no-throw, strong (rollback), basic (no leaks, invariants hold), or none.
- extern "C"
- Linkage specification that disables C++ name mangling so C, JNI and
dlsymcan see the symbol. - fold expression
- C++17 pack expansion that reduces a parameter pack with a binary operator.
- forwarding reference
- A template parameter
T&&that binds to both lvalues and rvalues and is forwarded withstd::forward. - glvalue
- A generalized lvalue: either an lvalue or an xvalue (an expression with identity).
- HIDL
- Legacy HAL interface language generating C++ over hwbinder; replaced by stable AIDL for new HALs.
- inline
- Permission for a definition to appear in multiple translation units; the linker merges them.
- iterator invalidation
- Rules describing when a container operation makes existing iterators, pointers or references unusable.
- JNI
- Java Native Interface: the boundary between ART/Java and C or C++ in the same process.
- lambda
- An anonymous function object generated by the compiler, optionally capturing enclosing variables.
- launder
std::launder: obtain a pointer the compiler must treat as referring to a new object in reused storage.- libc++
- LLVM C++ standard library; the STL used by the Android NDK.
- lvalue
- Expression with identity that is not expiring; you can take its address.
- make_shared
- Factory that typically allocates the object and the
shared_ptrcontrol block together. - memory_order
- Atomic constraint on visibility and ordering: relaxed, acquire, release, acq_rel, seq_cst.
- name mangling
- Encoding of C++ function types into linker symbol names so overloads can coexist.
- NDK
- Native Development Kit: Clang toolchain and headers for shipping C/C++ in Android apps and some system modules.
- noexcept
- Specifies that a function does not throw; enables optimizations such as moving on
vectorreallocation. - NRVO
- Named return value optimization: optionally constructing a named local directly in the caller’s return slot.
- ODR
- One Definition Rule: one definition per entity in the program, except identical inline/template definitions.
- override
- Specifier that a virtual function must override a base; mismatch is a compile error.
- PCH
- Precompiled header: serialized compiler state for a stable include prefix to speed builds.
- PIMPL
- Pointer to implementation: a private incomplete type behind a pointer, hiding ABI and reducing rebuilds.
- placement new
- Constructing an object in already-allocated storage; destruction is a manual destructor call.
- prvalue
- Pure rvalue: a temporary or initializer that does not yet have identity in the abstract machine.
- RAII
- Resource Acquisition Is Initialization: acquire in the constructor, release in the destructor.
- ranges
- C++20 library of lazy views and range-taking algorithms that operate on whole ranges instead of iterator pairs.
- RTTI
- Run-time type information:
typeidanddynamic_cast, often disabled with-fno-rttion Android. - Rule of Five
- If you define one of destructor, copy/move ctor, copy/move assign, define or delete all five.
- Rule of Zero
- Prefer types whose members already manage resources so you write no special members.
- rvalue
- xvalue or prvalue; can bind to
T&&. - SFINAE
- Substitution Failure Is Not An Error: a failed substitution removes a template candidate instead of failing the program.
- shared_ptr
- Shared-ownership smart pointer with an atomic strong count and a separate weak count in a control block.
- slicing
- Copying a derived object into a base value, losing the derived part and virtual behavior.
- SSO
- Small string optimization: short strings stored inside the
std::stringobject instead of on the heap. - STL
- Standard Template Library: containers, iterators, algorithms (and, loosely, the rest of the C++ standard library).
- strict aliasing
- Rule that an object may be accessed only through a compatible type (plus
char/byteexceptions). - this-adjustment
- Pointer offset applied when converting to a non-primary base or when dispatching a virtual call through that base.
- translation unit
- The text the compiler sees: one source file after preprocessing includes and macros.
- type erasure
- Hiding a concrete callable or type behind a uniform interface (
std::function,any) at the cost of indirection. - UB
- Undefined behavior: the standard imposes no requirements; compilers may assume it never happens.
- unique_ptr
- Move-only smart pointer with exclusive ownership and a possibly empty deleter (EBO).
- vtable
- Per-class table of virtual function pointers (plus RTTI / offset-to-top) used for dynamic dispatch.
- weak_ptr
- Non-owning observer of a
shared_ptrcontrol block used to break cycles and test liveness. - xvalue
- eXpiring value: has identity, but resources may be reused (the result of
std::moveon an object).
Interview questions
Fundamentals
What do you gain by moving from C to C++?
Deterministic destruction (RAII), a type system that can encode ownership and interfaces (references, const, overloading, templates), and the STL. You write fewer manual acquire/release pairs and you can express generic algorithms without macros. You still compile to the same kind of machine code and you still have UB if you break the abstract machine.
What C knowledge must you still have in a C++ interview?
Pointers and arrays, struct layout and padding, stack vs heap, the compilation and linking model, calling conventions, syscalls and file descriptors, and C undefined behavior (use-after-free, overflows, data races). Android native work constantly drops to that layer. See C.
What is RAII?
Resource Acquisition Is Initialization: the constructor acquires a resource (memory, fd, lock, mapping) and the destructor releases it. Scope exit, return and exception unwind all run destructors, so you do not write a cleanup path for every exit. Smart pointers and lock_guard are RAII. The kernel, being C, uses other patterns (devm_*, goto cleanup); that is a different world (Linux kernel & BSP).
What is a translation unit?
The text the compiler actually compiles: one source file after the preprocessor has expanded #include, macros and conditionals. Each .cpp is typically one translation unit and becomes one object file. Templates and inline exist because the compiler does not automatically see the whole program.
What is the One Definition Rule?
Each function, variable, class and template specialization may have only one definition in the program. Inline functions, inline variables and templates may be defined in multiple translation units if those definitions are identical. Two different definitions (often from a header compiled with different macros) are an ODR violation and undefined behavior, even if the linker is silent.
What does inline mean in modern C++?
It means the definition is allowed to appear in more than one translation unit and the linker must merge them. It is not a command to put the body in every call site; the optimizer inlines with or without the keyword. Put non-template function definitions in headers only if they are inline (or constexpr, which is implicit inline).
Why do we write extern "C"?
To give a function C linkage: no C++ name mangling, so a C caller, the dynamic linker, or the JVM (JNI) can find the symbol by its C name. It does not make the function "C" internally; it can still use C++ types in the body. You cannot overload two extern "C" functions with the same name.
What is name mangling?
The compiler encodes parameter types (and some qualifiers) into the linker symbol so foo(int) and foo(double) can both exist. On Android/Linux this is the Itanium ABI scheme (_Z...). nm or llvm-cxxfilt demangles. C symbols are not mangled this way, which is why mixed-language APIs need extern "C".
In what order are constructors and destructors run?
Virtual bases (from the most-derived constructor), then direct bases in base-list order, then members in declaration order, then the constructor body. Destruction is the exact reverse. Initializer-list order does not change member construction order. If a later member throws, already-constructed members and bases are destroyed.
What is a vtable?
A per-class table of pointers to virtual functions (plus RTTI and offset-to-top for multiple inheritance). A polymorphic object typically holds a hidden vptr to the vtable of its dynamic type. A virtual call loads the vptr, indexes a slot, may adjust this, and performs an indirect call.
What is the difference between a virtual and a non-virtual function?
A non-virtual call is resolved at compile time from the static type (and can be inlined easily). A virtual call is resolved at run time from the dynamic type through the vtable. Use virtual for "is-a" interfaces you will call through a base; do not make everything virtual (cost + it becomes part of your ABI).
What is a pure virtual function? What is an abstract class?
A virtual function declared = 0. A class with at least one unimplemented pure virtual is abstract: you cannot instantiate it. Derived classes must override it (or stay abstract). A pure virtual destructor still needs a definition, because derived destructors call it.
What is object slicing?
Copying or assigning a derived object into a base value copies only the base subobject. Virtual overrides and derived members are gone. Avoid it: pass polymorphic types by pointer or reference, and store unique_ptr<Base> in containers, not Base by value.
What is the difference between an lvalue and an rvalue?
An lvalue names an object that has identity and is not treated as expiring (a variable, *p). An rvalue is either a prvalue (a temporary / initializer like T{}) or an xvalue (something that is about to expire, like std::move(x)). Rvalues bind to T&& and can be moved from. "Left of equals" is the pre-C++11 slogan and is incomplete.
What does std::move actually do?
It is a cast to an rvalue reference: static_cast<T&&>(t). It does not move memory. Overload resolution may then pick a move constructor or move assignment. If none exists, or the object is const, a copy is used. After a move, STL objects are valid but unspecified (usually empty, still destructible).
When do you copy and when do you move?
Copy when you need two independent objects. Move when you are finished with the source and want to transfer its resources in O(1) (buffers, unique ownership). Pass cheap types by value; pass large read-only objects by const T&; pass transferable sinks by value or T&&. Return locals by name so NRVO / elision can apply.
What is the Rule of Zero?
If every resource is already owned by a member that knows how to copy, move and destroy itself (string, vector, unique_ptr), do not write a destructor or copy/move members. The compiler-generated ones are correct. This is the default for new C++ classes.
What are the Rule of Three and the Rule of Five?
Rule of Three (C++98): if you need a custom destructor, copy constructor or copy assignment (raw owning pointer), you need all three, or copies will double-free or leak. Rule of Five (C++11): writing any of those suppresses implicit moves, so you must also write or = delete the move constructor and move assignment. All five, or none.
unique_ptr vs shared_ptr vs weak_ptr?
unique_ptr is exclusive, move-only, zero overhead beyond the pointer (empty deleter can EBO). shared_ptr shares ownership with an atomic strong count in a control block; copies are not free. weak_ptr observes without keeping the object alive; lock() tries to promote to shared_ptr. Default to unique_ptr.
Why prefer make_unique and make_shared?
They pair allocation with construction so you do not write naked new. make_shared usually does one heap allocation for the object and the control block, and is exception-safer than shared_ptr<T>(new T) in expressions with multiple arguments. Use a custom deleter when you cannot.
Why was auto_ptr removed?
Copying an auto_ptr transferred ownership and nulled the source, so passing by value silently emptied the caller. It did not work in standard containers. unique_ptr is move-only and correct. C++17 removed auto_ptr.
How do new/delete differ from malloc/free?
new allocates and constructs; delete destroys and deallocates. malloc/free only deal in raw bytes and do not call constructors or destructors. Mixing them ( free of new, delete of malloc) is undefined. Prefer containers and smart pointers over both in C++ code.
What is placement new?
A new that constructs an object in storage you already have: new (buf) T(args). It does not allocate. You destroy with p->~T(), not delete p. Used by vector, optional and variant. The buffer must be aligned for T.
vector vs deque vs list: when would you use each?
vector is the default: contiguous, O(1) index, amortized O(1) append, best cache behavior. deque when you need O(1) insert/erase at both ends and still want random access. list when you need stable iterators and O(1) splice given a position; it is node-based and usually the wrong default because of cache misses. Interview default is vector unless you can name the extra need.
map vs unordered_map?
map is an ordered tree (typically red-black): O(log n), no hash, iterates in key order, stable iterators except for the erased node. unordered_map is a hash table: average O(1), worst O(n), needs a hash and equality, rehash invalidates iterators. Use map for order or for types that are painful to hash; use unordered_map for average speed and call reserve when you know the size.
What is small string optimization (SSO)?
std::string stores a short string inside the string object (commonly 15 characters on 64-bit libc++, sometimes more) and heap-allocates only when it grows past that. Moving a short string may copy those bytes; moving a long string steals a pointer. A char* obtained from c_str() dangles if the string reallocates or dies.
What is the difference between const and constexpr?
const means this access path will not mutate (plus some compiler assumptions for objects that were defined as const). constexpr means the function or variable can participate in constant evaluation: it may run at compile time if the arguments allow, and the same function can still run at runtime. A constexpr variable is implicitly const.
What does noexcept do?
It promises the function will not throw. If it does, the program calls terminate. The promise is used by the type system: vector relocates by move only when the move constructor is noexcept; otherwise it copies so a throw can still leave the container valid. Mark genuine non-throwing moves and swaps noexcept.
What is a data race in C++?
Two threads access the same memory location, at least one access is a write, the object is not atomic, and the accesses are not ordered by happens-before (a mutex, an atomic acquire/release pair, etc.). That is undefined behavior, not "last writer wins". Fix it with a mutex, with std::atomic, or by not sharing.
Give five examples of undefined behavior in C++.
Use-after-free or a dangling reference; using a vector iterator after a reallocation; signed integer overflow; a data race; accessing an object through an incompatible type (strict aliasing). Also: mismatched new/delete[], calling a virtual through a dangling pointer, and writing through const_cast to a truly const object.
What is a lambda expression?
A compiler-generated function object: a unique class with operator() and optional capture members. [] captures nothing; [=] copies; [&] references; prefer explicit [x, &y]. A capture-less lambda can convert to a function pointer. If the lambda outlives the captured locals, you have a dangling reference.
Why is the Linux kernel written in C rather than C++?
The kernel needs a tiny stack, no hidden allocations, no exceptions, no RTTI, a stable C ABI for modules, and control over every instruction in atomic context. C++ features (exceptions, constructors in unexpected places, name mangling, a heavy runtime) fight that. Linux is a C abstract machine plus kernel APIs. Userspace Android (daemons, HALs, NDK) is where C++ belongs. See Linux kernel & BSP.
What C++ standard library does the Android NDK use?
LLVM libc++, linked as c++_shared or c++_static. It is not GNU libstdc++. Do not pass std::string or containers across a boundary compiled with a different STL or ABI. Keep public .so APIs in C types, NDK binder types, or AIDL.
When do you choose unique_ptr over shared_ptr?
Almost always start with unique_ptr: exclusive ownership, no atomic traffic, clear lifetime. Use shared_ptr only when lifetime is genuinely shared and cannot be expressed as "parent owns child" (sometimes caches, graphs, or async callbacks). Shared ownership is a design smell if everything is shared.
Reference vs pointer?
A reference is an alias: it must be bound at initialization, cannot be reseated (except via implementation tricks you should not use), and is not nullable. A pointer is an object that holds an address: it can be null, reseated, and used in arithmetic. Use references for required aliases and function parameters; use pointers for optional or reseatable non-owning views, and smart pointers for ownership.
Going deeper
How do templates interact with the ODR and compile time?
Each used specialization is instantiated in the translation units that need it. Identical instantiations are merged at link time. Instantiating a heavy template in many TUs explodes compile time and object size. Mitigations: explicit instantiation in one .cpp plus extern template in the header, thinner headers, and (C++20, optional in interviews) modules.
What is a precompiled header?
A compiler snapshot of a stable prefix of includes so later files skip re-parsing them. It is a build-speed tool, not a language feature. If anything in the prefix changes, the PCH rebuilds. Android/Soong and many desktop builds use them for the expensive STL and platform headers.
What is ABI stability and what breaks it?
Compiled callers assume a layout and a calling convention. Adding a virtual function (vtable slots move), adding a data member, changing a member type, switching -fno-exceptions/-fno-rtti, or mixing libc++ with libstdc++ can break a shared library without a source-level error. PIMPL and C APIs are the usual firewalls. Android VNDK exists because this problem is real at OS scale.
What is the diamond problem and how does virtual inheritance help?
D inherits B and C, both inheriting A. Without virtual inheritance, D contains two A subobjects; names and conversions become ambiguous. Virtual inheritance shares one A, found via a vbase offset. It is slower and more complex; most Android code prefers a single abstract interface or composition instead of diamonds.
What is this-adjustment?
In multiple inheritance, a pointer to the second base is not the address of the complete object. Converting Derived* to Base2* adds an offset. A virtual call through Base2 may apply a thunk that adjusts this before jumping to Derived::foo. Single inheritance usually has offset 0.
What is the empty base optimization?
An empty class object still has size 1 so two instances have distinct addresses. An empty base can occupy zero bytes. unique_ptr<T, EmptyDeleter> can therefore be one pointer wide. C++20 [[no_unique_address]] allows a similar optimization for members. Interviewers use this to see if you know why a custom deleter's size matters.
Explain glvalue, prvalue and xvalue.
A glvalue has identity (lvalue or xvalue). A prvalue is a pure initializer/temporary (literals, T{}, a function returning T by value) and is the thing C++17 materializes into an object. An xvalue is an expiring glvalue: std::move(x), or a member of an rvalue. Move constructors bind to rvalues (xvalue or prvalue).
What is guaranteed copy elision vs NRVO?
Since C++17, initializing an object from a prvalue of the same type does not require a copy or move (the object is constructed in place). NRVO is optionally constructing a named local directly in the return slot; it is allowed but not required. return std::move(local) turns the local into an xvalue and blocks NRVO, so you may get a move instead of true elision. Return the name.
When must you write all five special members?
When the class owns a raw resource or maintains an invariant that member-wise copy/move would break, and you did not wrap that resource in an existing RAII type. Write a correct destructor, deep or deleted copies, and moves that steal and null the source. Self-assignment and moved-from destruction must be safe. Prefer wrapping and returning to Rule of Zero.
How does copy-and-swap implement assignment?
T& operator=(T other) { swap(*this, other); return *this; } — the parameter is copy-constructed or move-constructed. If that construction throws, *this is unchanged (strong guarantee). swap exchanges guts and should be noexcept. The parameter then destroys the old guts. Self-assignment is naturally safe because you work on a copy.
What lives in a shared_ptr control block?
A strong (use) count, a weak count, a deleter, and an allocator (type-erased). When the strong count hits 0 the deleter runs and the object is destroyed. When both counts hit 0 the control block is freed. weak_ptr increments only the weak count. Formula: destroy object iff strong==0; free block iff strong==0 and weak==0.
How does enable_shared_from_this work, and when does it fail?
The mixin stores a weak_ptr that shared_ptr's constructor arms when it takes ownership of an object that inherits the mixin. shared_from_this() locks that weak pointer. If no shared_ptr owns the object yet (stack object, raw new without immediately wrapping, or a second control block), you get bad_weak_ptr. Never construct shared_ptr<T>(this) yourself.
When do you use a custom deleter?
When release is not delete: fclose, close, free, ANativeWindow_release, HIDL release, or an arena. Put the deleter type in the unique_ptr signature. A function pointer costs a word; an empty functor can be EBO'd. shared_ptr type-erases the deleter in the control block so the pointer type stays shared_ptr<T>.
What is weak_ptr for in real systems?
Breaking parent/child or observer cycles so refcounts can hit zero; caches that should not keep the object alive; "call me if I still exist" callbacks (Android listeners, Binder death). You lock() to get a shared_ptr or skip the callback if expired. Android's wp<T> is the same idea in the old RefBase world.
How does a container allocator differ from new?
Containers allocate raw memory through an allocator, then placement-construct elements, and later destroy then deallocate. allocator_traits supplies defaults so a custom allocator can be small. vector<T, A> is a different type from vector<T>. C++17 pmr adds a runtime memory resource. Interviews want this mental model, not a full allocator implementation.
What are alignas and alignof?
alignof(T) is the alignment the type requires. alignas(N) requests at least that alignment on a variable or type. Over-aligned types (SIMD, cache-line padded atomics) need C++17 aligned operator new or a custom allocator. Placement new into an under-aligned buffer is UB.
What problem does std::launder address (light)?
After you destroy an object and placement-new a new one in the same bytes, old pointers can be assumed by the compiler to still refer to the old object (especially with const or reference members). std::launder(p) returns a pointer the compiler must treat as pointing at the new object. You rarely write it by hand; know the sentence for lifetime-reuse questions.
What is a forwarding reference and how does std::forward work?
In template<class T> void f(T&& x), T&& is a forwarding (universal) reference: lvalues make T a reference, rvalues make T a non-reference. std::forward<T>(x) casts x back to that category so a callee can move only from rvalues. A non-template T&& is just an rvalue reference and binds only rvalues.
Specialization vs overload: which should you use for functions?
Prefer extra function overloads (including additional function templates). Full specialization of function templates does not participate in overload resolution the way people expect and is easy to get wrong. For classes, full and partial specialization are the normal tools (vector<bool> is the infamous partial-specialization example; do not imitate its proxy-reference design).
What is SFINAE?
Substitution Failure Is Not An Error: if substituting template arguments into a function template's declaration fails, that candidate is discarded. enable_if, void_t and decltype in the signature are the pre-C++20 tools for "this overload exists only if T has X". Hard errors inside the body are still hard errors; the failure must be in the immediate context of the signature.
How do C++20 concepts compare to SFINAE?
Concepts name requirements (std::integral, std::ranges::range) and participate in overload resolution with better diagnostics. They replace most enable_if boilerplate. You should still recognize SFINAE in older codebases (including a lot of Android). Interviews often ask you to constrain a template both ways.
What is CRTP and when is it better than virtual functions?
Derived inherits Base<Derived>. Base calls static_cast<Derived*>(this)->impl(): compile-time polymorphism, no vtable, easy inlining, but each Derived is a different type so you cannot put them in one vector<Base*>. Use CRTP for mixins and static interfaces; use virtual when you need runtime heterogeneity.
What are variadic templates and fold expressions?
A parameter pack class... Ts holds zero or more types or values and expands with Ts.... Recursive instantiations used to process packs; C++17 folds reduce them: (0 + ... + args), (os << ... << args). make_unique and tuple factories forward packs with std::forward<Args>(args)....
How does vector growth give amortized O(1) push_back?
When capacity is exhausted, the vector allocates a larger buffer (libc++ doubles; some libraries use 1.5×), move-or-copies elements, and frees the old buffer. The cost of copies across a sequence of n pushes is a geometric series, so the average extra cost per push is constant. reserve(n) removes the reallocations if you know n.
Which operations invalidate vector iterators?
Any insert or push_back that grows past capacity reallocates and invalidates everything. Even without reallocation, insert in the middle invalidates from the insert point to the end. Erase invalidates from the erase point to the end. reserve that grows also invalidates. Holding data() across a growing push_back is the same bug.
What is the STL algorithms philosophy?
Separate containers from operations. Iterators are the glue. Name the algorithm (find_if, transform, lower_bound) instead of an ad-hoc loop so intent and edge cases stay correct. Algorithms require an iterator category (sort needs random access). Do not hand-roll a binary search in an interview if lower_bound applies.
What should you know about C++20 ranges in an interview?
Ranges algorithms take a range, not two iterators. Views (filter, transform) are lazy and usually non-owning: they dangle if the underlying container dies. They compose with |. You are not expected to implement a view; you should know they do not copy the data by default and that some views are not sized or not random-access.
What does the mutable keyword do?
On a data member, it may be modified from a const member function. Legitimate uses: a mutex protecting logical const, or a cache. On a lambda, mutable makes operator() non-const so by-value captures can be updated. It is not a way to dodge thread safety.
When is const_cast undefined behavior?
If the object was defined as const (or is a const member of a const object, or lives in ROM), any write through a stripped pointer is UB. Casting away const to call a C API that does not mutate is a common non-UB use. Prefer const-correct overloads so you do not need the cast.
consteval vs constinit (light)?
consteval (C++20): the function can only be called at compile time. constinit: this variable must be statically initialized (no dynamic constructor order surprises). Both are light interview topics; mention them as C++20 tools, not everyday NDK vocabulary.
What are the exception safety guarantees?
No-throw: cannot throw. Strong: completes or leaves state as before (copy-and-swap). Basic: no leaks and invariants hold, but state may have changed (typical STL container guarantee). No guarantee: leaks or corruption; unacceptable. State which one your function offers.
Why does Android system code often compile with -fno-exceptions?
Unwind tables cost size; an uncaught exception in a daemon or through JNI/Binder is fatal; APIs already use status codes (status_t, ScopedAStatus). The same builds often use -fno-rtti. App NDK modules may enable exceptions, but you must not throw into the VM or across a C callback. Destructors still must not throw.
lock_guard vs unique_lock?
lock_guard is the default RAII lock: lock in the constructor, unlock in the destructor; no API to unlock early. unique_lock can defer lock, unlock/relock, and is what condition_variable::wait requires because wait must unlock the mutex while sleeping. Prefer lock_guard unless you need those features. C++17 scoped_lock locks multiple mutexes deadlock-avoidingly.
Explain the common memory_order values.
relaxed: atomic RMW or load/store with no inter-thread happens-before. release store pairs with an acquire load: writes before the release become visible after the acquire. acq_rel is both on an RMW. seq_cst is the default single total order; easiest to reason about. Mutex lock/unlock already provide acquire/release.
How do you wait on a condition_variable correctly?
Hold a unique_lock on the same mutex that protects the predicate. Wait in a loop or with a predicate: cv.wait(lk, [&]{ return ready; });. The wait unlocks, sleeps, re-locks, and rechecks because of spurious wakeups and lost-wakeup races. Notify with notify_one or notify_all after changing the predicate under the mutex.
Is use-after-move undefined behavior?
Not automatically. The object is still alive and must be destructible and usually assignable. STL types specify "valid but unspecified" (often empty). Calling methods that assume old invariants (dereference a moved-from unique_ptr, use a moved-from container as if full) is a logic bug and can become UB. Do not rely on the source still holding its value unless the type documents it (e.g. moved-from unique_ptr is null).
Which lambda captures are safe to store on another thread?
Captures by value of the data you need, or shared_ptr / weak_ptr to the object, or C++17 [*=this] if a copy is correct. [&] and [this] require the referenced objects to outlive the task. This is a common SurfaceFlinger / thread-pool / Binder callback bug.
What does std::function cost?
Type erasure: a vtable-like call and, if the callable does not fit the small buffer, a heap allocation. Copying a function may allocate again. In a hot loop use a template, a function pointer, or a concrete functor. Fine for infrequent callbacks (UI, setup). std::move_only_function is C++23 (optional) for move-only callables.
How does JNI interact with C++?
Export extern "C" JNI functions or RegisterNatives from JNI_OnLoad. Do not let a C++ exception escape into ART; catch and ThrowNew. Manage local refs in long loops. Own Java objects with global refs inside RAII. Own native heaps that Java holds via a jlong and a disposer. See Java for the VM side.
Advanced
What is two-phase name lookup in templates?
At template definition time the compiler looks up non-dependent names. Dependent names (those that depend on a template parameter) are looked up again at instantiation. That is why you often need this->member or a using in a dependent base, and why a missing typename on a dependent type is an error. It explains "it compiled until I instantiated it" bugs.
What are explicit instantiation and extern template?
template class Foo<int>; in one .cpp forces that specialization to be emitted there. extern template class Foo<int>; in headers tells other TUs not to emit it. This cuts compile time for widely used specializations. It is an ODR/build-hygiene tool, not a new language meaning of the template.
How does virtual dispatch work under multiple inheritance?
Each base that introduces virtuals typically has its own vptr. A call through Base2* uses Base2's vtable. The slot may point at a thunk that subtracts (or adds) the this offset and then jumps to Derived::fn. Offset-to-top in the vtable recovers the complete object for dynamic_cast and delete. Formula: (*vptr[i])(this + δ).
trivial vs standard-layout vs POD?
Trivial: the compiler can treat copy/move/destroy as memcpy-ish (no user special members, no virtuals, trivial members). Standard-layout: C-like layout (one control block of access, no virtuals, consistent types) so you can interoperate with C and offsetof. POD is the old name for roughly both. A virtual function kills both. Android HAL C structs should stay standard-layout.
What ends an object's lifetime?
The destructor starts (or the storage is reused / released). Storage duration is separate: automatic, static, thread, dynamic. A pointer to storage is not a pointer to an object after lifetime ends. Placement new starts a new lifetime in the same bytes. Returning a reference to an automatic object ends lifetime at the } and dangles.
When do you reuse storage and why mention launder?
Optional/variant/union-like types destroy T and construct U in the same buffer. After that, the compiler may assume a pointer still refers to the old T if T had const or reference members. std::launder is the standard way to get a pointer to the new object. In interviews, pairing "placement new + explicit dtor + alignment + launder if needed" is a complete answer.
What does allocator_traits add that a raw allocator might omit?
Defaults: construct/destroy via placement new and explicit dtor, rebind to allocate a different type (list nodes), pointer typedefs, and propagation traits (whether a container copy copies the allocator). You can write a minimal allocator with just allocate/deallocate and let the traits fill the rest.
What is type erasure and where do you see it?
A uniform value type that can hold many concrete types via a hidden vtable or function pointers: std::function, std::any, shared_ptr's deleter, some Binder type-erased callbacks. You pay an indirect call and often a heap allocation. Contrast with templates (open at compile time, no single type) and virtual bases (intrusive, one hierarchy).
When would you still write SFINAE instead of a concept?
When you are on C++17 (common in older NDK and vendor trees), when you must match an existing trait-based API, or when the constraint is a quick void_t detect-member trait. In C++20 new code, prefer a concept or a requires clause. Both implement "this overload exists only if".
Write a fold that prints a pack. What is the empty-pack catch?
(std::cout << ... << args); is a binary left or right fold depending on placement. A unary fold over an empty pack is ill-formed for most operators; &&, || and , have defined empty values. Always think about the zero-argument call of a variadic function.
What are C++20 modules, at interview depth?
A replacement for textual includes: a module is compiled once and imported as a semantic unit, which can cut compile time and stop macro leakage. Android builds are still header-dominated; treat modules as optional knowledge. Do not claim the NDK is modules-first unless the interviewer is exploring the standard, not the platform.
What is the spaceship operator?
C++20 operator<=> returns a comparison category (strong_ordering, partial_ordering, …). The compiler can synthesize == and the relational operators. Useful for writing one comparison instead of six. Optional follow-up: partial_ordering for floats because NaN.
What does happens-before mean in the C++ memory model?
It is the partial order that makes a write visible to a later read. Sequenced-before (same thread) plus synchronize-with (mutex unlock/lock, release/acquire atomics, thread create/join) compose into happens-before. If a write does not happen-before a read of the same location, and they conflict, you have a data race unless the location is atomic.
What is the ABA problem?
A lock-free algorithm reads A, another thread changes A to B and back to A, and a compare-exchange succeeds as if nothing happened even though the meaning of A changed (for example a freed-and-reallocated node). Tagged pointers, hazard pointers, epoch reclamation (RCU-like) or just using a mutex are the usual answers. Mention it if they ask about lock-free stacks.
What is std::exception_ptr for?
It captures the current exception (current_exception) so you can store it, pass it across threads, and rethrow_exception later. Useful when a worker cannot throw into the thread that must handle the error. On -fno-exceptions builds this machinery is not available; you pass error codes instead.
Why must move constructors of container elements often be noexcept?
If move can throw, vector reallocation copies instead, so a throw mid-copy can destroy the extra elements and still leave the original buffer intact (strong guarantee). If you mark a throwing move noexcept, a throw during reallocation calls terminate. Implement moves that only steal pointers and mark them noexcept.
What is ADL and why does swap rely on it?
Unqualified lookup also searches namespaces associated with the argument types. using std::swap; swap(a, b); finds a friend swap for your type if you provided one, otherwise std::swap. That is why a hidden friend swap is the recommended customization point. The same mechanism finds operator<<.
What is the most vexing parse?
T x(U()); is parsed as a function declaration (x returns T, takes a function taking U), not an object. Fix with braces: T x{U{}}; or extra parentheses. It is a C++03 leftover that still bites people who write functional casts as constructors.
When is a temporary's lifetime extended?
Binding a temporary to a local const T& or T&& extends its lifetime to that reference. The extension does not pass through a function that returns a reference to its parameter: const T& f(const T& x){ return x; } const T& r = f(T{}); dangles. Member references and std::tuple of references also do not extend in the way people hope.
What is the strict aliasing rule?
You may access an object only through a glvalue of a compatible type (same type, similar, or char/unsigned char/std::byte). Casting a float* to int* and dereferencing is UB; the compiler will assume they cannot alias and reorder loads. Use memcpy, std::bit_cast (C++20), or a union only in the narrow cases the standard allows (and prefer memcpy/bit_cast).
Why is signed overflow UB while unsigned wrap is defined?
The standard says unsigned arithmetic is modulo 2n. Signed overflow is UB so compilers can assume x + 1 > x for signed x and can widen to a larger register. Use unsigned (or a checked API) for wraparound; use a wider type if you need to detect overflow. Sanitizers flag signed overflow.
Why did lambdas replace most of std::bind?
Lambdas are readable, have obvious capture lifetimes, and compose without nested bind placeholders. bind copies arguments, is hard to overload, and surprises people with nested binds. Read bind in old code; write a lambda. std::bind_front (C++20) is a narrower, saner leftover for partial application.
HIDL C++ vs AIDL NDK C++: what changes for a HAL author?
HIDL: hidl_string/hidl_vec, sp<IFoo>, hwbinder, hwservicemanager, no new HALs. AIDL NDK: ordinary std::string/vector (with ABI caution), ndk::ScopedAStatus, AIBinder, servicemanager or vndbinder, required for vendor. Both are Binder; the type system and process rules differ. Details: Binder & AIDL.
Why are SurfaceFlinger and netd written in C++?
They are long-lived, privileged, performance-sensitive userspace daemons: composition every vsync, and networking policy/ioctls. C++ gives RAII and types without the GC pauses of Java. They are not the kernel; they talk to drivers through HAL and syscalls. See Android frameworks.
Why can't you pass std::string from a libc++ .so to a libstdc++ .so?
Different ABIs: layout of string (SSO, pointers), vector, typeinfo, and exception types. The symbols are mangled in related but not interchangeable ways. The call may link if you are unlucky and then corrupt the heap. Use a C API, a POD struct, or a serialization format at the boundary.
What is PIMPL and when do you use it?
A class holds a unique_ptr to an incomplete Impl defined only in the .cpp. Clients do not rebuild when Impl changes; the public class's size stays one pointer. Cost: an extra allocation and a pointer hop. Used to stabilize ABI and to hide platform headers from public headers.
What is std::span and how is it not a container?
C++20 non-owning view: pointer plus length (or a static extent). It does not allocate and does not extend lifetime. Use it for function parameters that need a contiguous range without forcing vector. Dangling is the same as a pointer. Prefer it over raw pointer + size pairs.
Name a few C++23 features and mark them optional for interviews.
Optional: std::expected, std::mdspan, std::print / println, explicit object parameters ("deducing this"), std::flat_map / flat_set, std::move_only_function. Android NDK language level often lags. Lead with C++11–20 unless they ask about 23.
Why is a virtual call in a constructor not the derived override?
Construction runs base then members then derived. While the base constructor runs, the object's dynamic type is the base; the vptr points at the base vtable. The derived object is not yet formed, so calling a virtual "hook" from the base constructor will not reach derived. Use a two-phase init or a factory after construction completes.
What should you know about C++20 coroutines in an interview?
Light/optional: co_await / co_return split a function into a state machine on the heap (unless optimized). They need a promise type and an executor/awaitable. Android framework code does not generally use them yet. If asked, say they are for async state machines and that lifetime of captured frames is the hard part; do not pretend they are how Binder works.
How do you sketch unique_ptr on a whiteboard?
Members: T* ptr and a deleter. Delete copies. Move steals and nulls. Destructor calls the deleter if non-null. reset deletes then replaces; release returns the pointer and nulls. Provide *, ->, get. Mention array specialization and EBO for empty deleters. Then say you would use the standard type.
Scenario & debugging
A function returns const std::string& to a local string. The caller crashes later. Why?
The local's lifetime ends at the closing brace. The returned reference dangles. Any use is UB (often a use-after-free when SSO spilled to the heap, or a wild stack read). Fix: return std::string by value and let elision/move handle it, or take an output parameter. Binding the return to a const string& in the caller does not extend a lifetime that already ended.
You std::move a string into a worker, then log the original. What can go wrong?
The original is valid but unspecified: it may be empty. Logging it is not necessarily UB, but you will not see the old text. If you then call an API that assumes a non-empty path (open a file), you get a logic failure. If you had a raw pointer into the old buffer, that pointer is now dangling. Do not use the source except to assign or destroy, unless the type documents more.
A shared_ptr graph never frees. How do you find the leak?
Look for cycles: parent owns child and child owns parent, or two listeners hold shared_ptrs to each other. Break one edge with weak_ptr. Tools: ASan leak mode, heap dumps, or logging custom deleters. Also check enable_shared_from_this callbacks that keep a shared_ptr in a global list forever.
A crash in a loop that push_backs while holding an iterator. Diagnosis?
Classic vector reallocation: capacity grew, the buffer moved, the iterator is dangling. Fix: reserve first, use indices, or structure the loop so you do not hold iterators across growth. The same bug happens with a pointer from data() or a reference to an element.
A class has a destructor that deletes a raw pointer but uses the default copy constructor. What happens?
Two objects share one allocation. The first destructor frees it; the second is a double-free (UB). Assignment leaks the destination's pointer then double-frees later. This is the Rule of Three/Five failure. Fix: unique_ptr (Rule of Zero) or write all five members correctly (deep copy or deleted copies).
Two threads increment a plain int counter. Is that just a race you can ignore?
No. It is a data race and therefore UB. You may see lost updates, torn reads, or "impossible" optimizations. Use std::atomic<int> (relaxed is enough for a pure counter) or a mutex. "It works on x86" is not a C++ answer.
A constructor throws after some members were constructed. Who cleans up?
Completed bases and members are destroyed in reverse order. The object never started its lifetime, so the destructor of the class itself does not run. Resources held only as raw pointers assigned in the body can leak; RAII members do not. This is a standard argument for acquiring resources in member constructors, not in the body after a naked new.
A destructor throws during stack unwinding. What happens?
If another exception is already in flight, std::terminate is called. Even without that, throwing from a destructor is banned by style and by noexcept destructors (the implicit destructor is noexcept if all members are). Swallow, log, or abort deliberately; do not throw.
You store derived objects in vector<Base> and virtual calls do the base thing. Why?
Slicing: the vector holds Base values. Use vector<unique_ptr<Base>> (or a pointer/reference wrapper to existing objects). Also ensure Base has a virtual destructor if you delete through Base*.
Two threads deadlock on two mutexes. How do you explain and fix it?
Classic AB-BA: thread 1 holds A waits for B; thread 2 holds B waits for A. Fix: a global lock order, std::scoped_lock(a, b) (tries to avoid deadlock), or one mutex. On Android also think about Binder: do not hold a lock across an outgoing transaction that may re-enter. See Binder & AIDL.
A long JNI loop creating Java strings eventually fails. What did you forget?
Local references are only batched until the native method returns. In a tight loop, create a local frame or DeleteLocalRef each iteration. Also check for ExceptionOccurred after JNI calls. This is a C++ / JNI lifetime problem, not a Java GC mystery.
A vendor HAL process should own a hardware session. Which smart pointer?
Exclusive session: unique_ptr with a custom deleter that powers down the device, or an RAII class. Shared between callbacks: shared_ptr plus weak_ptr for listeners so the HAL can die. Do not use raw new with a manual close on some paths only. AIDL NDK types like ndk::ScopedAStatus already follow RAII for the IPC result.
You take T* to v[0] then v.push_back. Later dereference crashes. Why?
If push_back reallocated, v[0]'s address changed. The pointer is dangling. reserve before taking the pointer, or take the pointer only after the vector is stable, or use an index.
You add a virtual function to a class in a shared library. Old apps crash. Why?
ABI break: vtable layout and object size (hidden vptr if it was the first virtual) changed. Old binaries still use the old offsets. This is why public NDK APIs and VNDK freeze layouts, and why PIMPL or a C API is used at stable boundaries.
A hot audio or composition path copies shared_ptr every callback. What do you say?
Each copy is an atomic increment/decrement on the control block: cache-line ping-pong. Prefer a raw observer with a documented lifetime, a weak_ptr locked rarely, or a unique owner on the real-time thread. Measure; do not sprinkle shared_ptr in per-frame code out of habit. SurfaceFlinger-style paths care about this.
shared_from_this() throws bad_weak_ptr in a constructor. Why?
The object is not yet owned by a shared_ptr; the mixin’s weak pointer is empty. Construct with make_shared (or a factory that wraps immediately) and call shared_from_this only after that. Do not call it from the constructor of the object being made.
ASan reports new[] vs delete mismatch. What is the language rule?
new T[n] must be released with delete[] so the compiler can destroy every element and pass the right size to the deallocator. delete on an array is UB. Prefer unique_ptr<T[]> or vector<T> so the pairing is automatic.
A std::thread captures this and the object is destroyed. What happens?
The thread still runs this->... on freed memory: use-after-free, UB. Join the thread in the destructor (and define a shutdown order), or capture a shared_ptr / weak pointer, or do not start a thread that outlives the object. Detached threads make this worse because you cannot join.
A system daemon is built with -fno-exceptions but a library throws. What do you expect?
There is no unwind through that TU: terminate, or a worse abort if the exception personality is missing. Treat it as a process killer. The fix is to keep exceptions inside the library, compile the whole program consistently, or use error codes at the boundary. This is why Android native APIs prefer status returns.
A template error is three pages long. How do you talk through it?
Read the first instantiation that failed and the bottom note (the actual constraint or missing member). The middle is the call stack of templates. With C++20, a concept failure names the requirement. In C++17, look for enable_if / no type named .... Do not start rewriting from a random line in the STL.
Someone const_casts a const object and writes to it "because it compiled". Your answer?
If the object was born const, that write is UB. The compiler may keep the original value in a register or place the object in read-only memory (crash on write). If they needed mutation, the object should not have been const, or they should use mutable for a real cache under a lock.
A lock-free flag uses relaxed stores to publish a pointer. Readers see a garbage object. Why?
Relaxed gives atomicity of the flag or pointer word, not visibility of the fields written before it. The writer must release-store the pointer (or the flag) after initializing the object; the reader must acquire-load. A mutex around both sides also works. This is the "publish a pointer" interview pair.
A helper takes Base b and you pass a Derived. Tests fail only on virtual behavior. Why?
Slicing at the call boundary: the parameter is a new Base. Change the helper to Base& or const Base&, or pass unique_ptr<Base>. This is the same bug as vector<Base>.
Write an RAII file-descriptor wrapper. What do interviewers ding?
Missing deleted copies (double close), missing move that sets the source to -1 (double close again), closing -1, not handling self-move, and throwing from the destructor if close fails. Show the five members or a unique_ptr with a deleter that calls close. See the sketch on this page.
When is list the wrong answer in a coding interview?
When the problem needs index access, binary search, or cache-friendly scans: vector wins. list is for splice and stable node pointers. Saying "list because insert is O(1)" without "given an iterator" and without cache cost is a weak answer. See DSA for complexity framing.
A condition_variable wait never returns even though notify ran. What did you miss?
Lost wakeup: notify happened before wait, and you did not check the predicate under the mutex. Always change the predicate under the lock, then notify; wait with a predicate loop so a notification that already happened is still seen. Also: using a different mutex on the two sides, or notify_one when several waiters need to run.
A weak_ptr callback does nothing. Is that a bug?
Often it is correct: the object died and lock() failed. Confirm the owner's lifetime (was the shared_ptr dropped too early?). If the callback must run, you needed shared ownership or a queued teardown. If it must not keep the object alive, empty lock is the feature, not the bug.
You placement-new into a buffer every reuse and never call the destructor. What goes wrong?
The old object's destructor never runs: leaks (if it owned heap), skipped side effects, and then you construct a second object in the same bytes without ending the first lifetime (UB). Pair every placement new with p->~T() before reuse or scope exit. Prefer optional<T> or a container.
You return std::move(local); and someone says it is slower. Are they right?
Often yes: you block NRVO, so you force a move instead of constructing in place. Return local;. std::move on a return is for returning a member or a parameter you want to treat as an rvalue, not for a local of the return type.
delete p where p is Base* and the destructor is not virtual. What is the interview answer?
Undefined behavior if the dynamic type is derived: only ~Base runs, derived members leak, and the deallocation size may be wrong. Make the destructor virtual (or protected-nonvirtual if you never delete through Base). This is asked constantly.
std::async's future is destroyed at the end of the statement. Why did the program serialize?
A future from std::async with default launch may block in its destructor until the task finishes. async(f).get() or even discarding the future can run the work synchronously from the caller's point of view. Prefer an explicit thread pool or store the future if you wanted overlap. Mention this when they ask about async.
unordered_map lookups are suddenly O(n). What do you check?
A bad or colliding hash (all keys in one bucket), an adversarial key set, or a custom hash that is constant. Fix the hash, use a better mixer, or switch to map if n is moderate and you need worst-case bounds. reserve does not fix a broken hash. Mention load factor and bucket count.
You copy-assign a resource-owning class without a self-assignment check. When does it blow up?
a = a; (or overlapping aliases) frees the resource then copies from the freed pointer. Copy-and-swap is naturally self-assignment safe. A handwritten assign should either check this != &other or copy to a temporary first. Interviewers still like the explicit check plus a correct order: copy, then release, then take.
A Binder HAL callback runs on a binder thread and deadlocks your mutex. Walk through it.
You held the mutex, made an outgoing Binder call, the other side called back into the same process on a binder thread, and that thread tried to take the same non-recursive mutex. Fix: never hold the lock across IPC; copy the data, drop the lock, then transact; or document a lock order that excludes Binder. Same pattern as Java lock + Binder. See Binder & AIDL.
An NDK library linked c++_static and another linked c++_shared both pass std::string. What happens?
Two copies of libc++ (or mixed ABIs) mean two heaps and two string layouts. Crossing the boundary can free with the wrong allocator or corrupt SSO. Keep STL types inside one linkage domain; expose C or AIDL types at the .so edge. This is an Android-specific ABI question.