1. What is the precise effect of invoking std::move(x) on an object x in C++?
Answer: std::move(x) is purely a static cast of x to an rvalue reference — it performs no runtime resource transfer by itself.
The most important thing to understand is that std::move does nothing at runtime. It doesn’t free memory, doesn’t copy data, doesn’t invalidate the object. It is a cast: static_cast<std::remove_reference_t<T>&&>(x). That’s all.
Its purpose is to change the value category of the expression x from lvalue to rvalue (specifically an xvalue). That change matters only because of how overload resolution works: when you pass the result to a constructor or assignment operator, the compiler now prefers the overload that takes an rvalue reference — i.e., the move constructor or move assignment — instead of the copy.
So the actual transfer of resources (stealing a buffer pointer, moving heap data) happens later, in the move constructor or move assignment the value is passed into. std::move merely enables that choice.
The common misconception is “std::move moves data.” It doesn’t — it labels the value so that other code will move it. The interview answer: std::move is an unconditional rvalue cast with zero runtime effect; resource transfer occurs only when the result feeds a move constructor or move assignment.
2. What occurs if foo() is invoked inside B’s constructor during the instantiation of a derived class object D?
Answer: The base version B::foo() executes, not the derived override.
This is the “virtual calls don’t dispatch during construction” rule. The reason is initialization order. When you create a D, the base subobject B is constructed first. During B’s constructor, the object’s virtual table pointer (vptr) is set to B’s vtable — the derived class D hasn’t started constructing yet, and its members don’t exist.
So when B’s constructor calls the virtual foo(), the runtime looks up the vptr and finds B’s vtable. The call resolves to B::foo(). This is deliberate: calling D::foo() would execute code that may read D’s data members — which are not yet initialized (in fact, for data members they’d be uninitialized, and for the vptr of D they’re not even set). The language prevents this hazard by fixing the dynamic type to B for the duration of the base constructor.
The same rule applies in the destructor: during ~B, the vptr reverts to B’s vtable, and virtual calls resolve to the base version.
The interview answer: B::foo() runs, because the vptr points to the base vtable until the derived constructor begins — virtual dispatch during base construction resolves to the base.
3. What occurs when two objects holding std::shared_ptr instances reference each other, forming a circular dependency?
Answer: A memory leak — both reference counts stay at least 1 forever, so neither object is ever deleted.
std::shared_ptr manages lifetime with a reference count: the object is destroyed when the count drops to zero. A cycle breaks that mechanism. If A holds a shared_ptr to B and B holds a shared_ptr to A, then:
- A’s count is at least 1 (B references it).
- B’s count is at least 1 (A references it).
Even when every external pointer goes out of scope, the two objects keep each other alive by mutual reference. The count never reaches zero, so neither destructor runs. That’s a leak — the classic reference-counting cycle problem (also why Python’s GC needs a cycle collector).
The fix is to break the cycle with std::weak_ptr: one link in the cycle is a non-owning weak reference that doesn’t increment the count. Typically the “owner” uses shared_ptr and the “back-pointer” uses weak_ptr. When the owning side drops away, the count hits zero and everything is cleaned up — the weak_ptr simply expires.
The interview answer: circular shared_ptr references leak; break cycles with weak_ptr.
4. Which operational guarantee does a function offer if it satisfies the “Strong Exception Guarantee”?
Answer: Commit-or-rollback: if an exception is thrown, all state changes are rolled back, leaving the program in exactly the state it was before the call.
The strong guarantee is the strictest of the three exception safety levels (alongside basic and no-throw). Its contract: either the operation completes fully and the new state takes effect, or an exception is thrown and the state is unchanged — as if the call never happened.
Implementing it typically means doing the dangerous work on copies or temporaries first (for example, building a new buffer, then swapping it into place), and only committing to the visible state with a no-fail operation like swap. If construction throws partway, the original state is still intact because nothing visible was touched yet.
Contrast with the basic guarantee, which only promises no resource leaks and that objects remain in a valid (but possibly modified) state after an exception. The strong guarantee is stricter: not just valid, but identical.
The interview answer: strong exception guarantee = commit-or-rollback; on exception, state is fully rolled back to the pre-call condition.
5. What is the fundamental operational difference between std::move and std::forward in template metaprogramming?
Answer: std::move unconditionally casts its argument to an rvalue reference. std::forward<T> conditionally casts — it preserves the original value category of the argument passed to a forwarding (universal) reference.
Both are casts (no runtime work), but they answer different questions.
std::move(x) — “I don’t care about x anymore; treat it as an rvalue always.” Unconditional.
std::forward<T>(arg) — used with a forwarding reference T&& in a template. T is deduced to be T& if the caller passed an lvalue, or T if they passed an rvalue. forward then casts the argument back to that original category: lvalue stays lvalue, rvalue stays rvalue. This is “perfect forwarding” — the template forwards parameters to another function while preserving exactly how they were passed.
The rule of thumb: use move when you own the value and want to move it; use forward when forwarding a parameter through a template. The interview answer: move is unconditional, forward is conditional and preserves the original value category through forwarding references.
6. What functionality does the mutable specifier provide when applied to a non-static class member variable?
Answer: It allows the member to be mutated inside const member functions.
The const on a member function makes the this pointer const-qualified: the function promises not to modify the object. A mutable member is exempt from that promise. Even in a const member function, a mutable member can be read and written.
Why would you want that? There are two classic cases:
- Logical vs. bitwise constness: an object is logically const (its externally observable data never changes), but it has internal bookkeeping that needs updating. The canonical examples are a mutex (locking it must not require a non-const object) and a memoization cache (lazily computing and storing a result is fine even on a const object).
- Reference counters or debugging statistics.
So mutable doesn’t add thread-safety and doesn’t change memory layout — it only relaxes the const restriction for one specific field. The interview answer: mutable permits modification of that member inside const member functions, used for things like mutexes and caches.
7. According to the C++ Strict Aliasing Rule, which pointer dereference pattern results in Undefined Behavior?
Answer: Reinterpreting a float object by casting its address to an int* and dereferencing it.
The strict aliasing rule says: you may only access an object’s storage through a pointer of a compatible type. Accessing a float as an int violates this — the compiler is allowed to assume two objects of different incompatible types don’t overlap, and it optimizes on that assumption. When you break the rule, you get undefined behavior: the optimized code may behave unexpectedly.
The exception list — types you may alias through:
- Character types —
char*,signed char*,unsigned char*can examine the bytes of any object. std::byte*— the byte-aliasing exception.- Signed/unsigned variants of the same type.
- Base/derived related types.
Everything else — like float accessed as int — is off-limits. This is why reinterpret_cast<float*>(&intVar) then dereferencing is a bug, and why the correct way to inspect an object’s bytes is to cast to char*/std::byte* (or memcpy).
The interview answer: dereferencing a float through an int* violates strict aliasing and is undefined behavior; byte access must go through char/std::byte pointers.
8. How does a C++ class containing virtual functions incur runtime memory and execution overhead?
Answer: Each instance carries a hidden virtual pointer (vptr) to a per-class virtual table (vtable), adding a pointer-sized memory cost and an indirection on each virtual call.
When a class declares at least one virtual function, the compiler adds a hidden pointer to each object: the vptr. All objects of the same class point to the same static vtable — an array of function pointers, one per virtual function, shared by the whole class.
Two costs:
- Memory: one pointer per instance (typically 8 bytes), even if the class has no data members.
- Dispatch: a virtual call is not a direct call. The runtime loads the vptr, indexes into the vtable, and calls through the function pointer — an extra indirection compared to a non-virtual call. In practice this is cheap, but it exists, and it also defeats some compiler inlining opportunities.
The trade-off buys runtime polymorphism: the same virtual call on a Base* can resolve to different derived implementations depending on the object’s actual type. The interview answer: a per-instance vptr to a shared static vtable — pointer-sized memory overhead per object plus an indirection on virtual dispatch.
9. What is the difference between constexpr and C++20 consteval?
Answer: A constexpr function may evaluate at compile time or runtime; a consteval function (an “immediate function”) must evaluate at compile time.
constexpr is flexible: if you call it with constant expressions, the compiler can compute it at compile time (and typically does); if you call it with runtime values, it falls back to normal runtime execution. It’s a “may run at compile time” function.
consteval (C++20) removes the fallback: the function must be evaluated at compile time. Calling it with anything other than a constant expression is a compile error. It’s a “must run at compile time” function.
The use cases follow:
constexpr— a function that should be usable in constant contexts (array sizes, template args) but also fine at runtime.consteval— a function you intend only for compile-time computation, typically because its result is required in a constant context, or to guarantee no runtime cost ever.
The interview answer: constexpr = compile-time or runtime; consteval = compile-time only, non-constant calls are rejected at compile time.
10. What does the “Rule of Zero” advocate in modern C++ object design?
Answer: Classes that don’t directly manage raw resources should declare no special member functions at all — letting compiler-generated defaults handle everything, because the class owns its resources via RAII types like std::unique_ptr, std::vector, and std::string.
The classical “Rule of Three” said: if you define a destructor, you almost certainly need the copy constructor and copy assignment too. The “Rule of Five” (C++11) added move operations. The Rule of Zero observes that you usually shouldn’t define any of them.
If a class’s members are all RAII types — smart pointers, containers, standard strings — those members already know how to copy, move, and destroy correctly. The compiler-generated copy/move/destructor simply delegate to them, and they work flawlessly. Writing your own destructor (or any special function) is unnecessary code, and worse, declaring certain special members suppresses others and can silently change behavior.
The goal is: no hand-written special members, no raw new/delete, no manual resource handling. Resources are managed by library types, and the class gets correct behavior for free. (The “Rule of Five” then only applies to the rare class that genuinely manages raw resources.) The interview answer: the Rule of Zero says avoid writing destructors/copy/move functions; manage resources through RAII types so compiler defaults are correct.
Premium Content
Unlock Top 10 - Part 1 and all premium lessons with a subscription.
From ₹199.99/year — See plans