C++ course menu
Advanced C++ Interview Questions and Answers
Advanced C++ interview questions with answers: move semantics and std::move, perfect forwarding, lvalues and rvalues, copy elision, templates and concepts, constexpr, inline and the ODR, memory layout and padding, data races and atomics, and undefined behaviour.
On this page
- Value categories and moves
- 1. What does std::move actually do?
- 2. What are lvalues and rvalues?
- 3. What is perfect forwarding?
- 4. What is copy elision / RVO?
- 5. Why should move constructors be noexcept?
- Templates and compile time
- 6. What are C++20 concepts, and why are they useful?
- 7. What is the difference between const and constexpr? What is consteval?
- 8. What is the One Definition Rule (ODR), and what does inline mean today?
- 9. Why are templates usually defined in header files?
- Memory and performance
- 10. What is struct padding, and how can you reduce it?
- 11. Why can std::vector beat std::list even for insertions?
- 12. What is false sharing?
- Concurrency and correctness
- 13. What is a data race, and how do you prevent one?
- 14. What is the difference between a mutex and an atomic?
- 15. Give examples of undefined behaviour that optimizers exploit.
- Next steps
Senior and systems-programming interviews probe the parts of C++ that make it fast: value categories and moves, templates, compile-time computation, memory layout and concurrency. Precise vocabulary matters here.
Value categories and moves#
1. What does std::move actually do?#
Show solution
Nothing at run time. std::move(x) is a cast to an rvalue reference (static_cast<T&&>(x)). It tells overload resolution that x may be moved from, so a move constructor or move assignment is chosen instead of a copy. The move itself happens in that function. After a move, the source is in a "valid but unspecified" state.
#include <iostream>
#include <string>
#include <utility>
int main() {
std::string a = "a long string that will not use the small buffer";
std::string b = std::move(a);
std::cout << b.size() << ' ' << std::boolalpha << a.empty() << '\n';
}48 truea being empty is what GCC, Clang and MSVC do in practice, but it is not guaranteed by the standard.
2. What are lvalues and rvalues?#
Show solution
Every expression has a value category. Simplified: an lvalue has an identity and can be on the left of = (a variable, *p, v[0]); a prvalue is a pure temporary value (42, a + b, a function returning by value); an xvalue is an object about to expire (std::move(x)). Rvalue references (T&&) bind to prvalues and xvalues, which is how move semantics detects objects that can be safely stolen from.
3. What is perfect forwarding?#
Show solution
A function template that passes its arguments on unchanged in value category: lvalues stay lvalues and rvalues stay rvalues. It uses a forwarding reference (T&& where T is deduced) plus std::forward<T>:
#include <iostream>
#include <string>
#include <utility>
void take(const std::string&) { std::cout << "copy\n"; }
void take(std::string&&) { std::cout << "move\n"; }
template <typename T>
void wrapper(T&& arg) {
take(std::forward<T>(arg));
}
int main() {
std::string s = "hello";
wrapper(s); // lvalue in, lvalue out
wrapper(std::string("hi")); // rvalue in, rvalue out
}copy
movestd::make_unique, emplace_back and std::thread's constructor all use perfect forwarding.
4. What is copy elision / RVO?#
Show solution
The compiler constructs a returned object directly in the caller's storage, skipping the copy or move. Since C++17 it is guaranteed when returning a prvalue (return Widget(...);). Returning a named local (return result;, NRVO) is usually elided too, and otherwise moved automatically. Writing return std::move(result); can block NRVO, so don't.
5. Why should move constructors be noexcept?#
Show solution
std::vector must keep its strong exception guarantee when it reallocates. If an element's move constructor might throw, a failure halfway through would leave elements in both buffers, so the vector copies instead (via std::move_if_noexcept). Marking moves noexcept lets containers move, which is much faster.
Templates and compile time#
6. What are C++20 concepts, and why are they useful?#
Show solution
Concepts are named compile-time requirements on template parameters:
#include <concepts>
#include <iostream>
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <Numeric T>
T clamp_to_zero(T value) {
return value < 0 ? T{0} : value;
}
int main() {
std::cout << clamp_to_zero(-5) << ' ' << clamp_to_zero(2.5) << '\n';
}0 2.5They give clear error messages at the call site, document intent, and allow overloading on constraints, replacing complex SFINAE tricks.
7. What is the difference between const and constexpr? What is consteval?#
Show solution
const: cannot be modified after initialization; the value may be known only at run time.constexprvariable: must be initialized with a constant expression, so it is usable at compile time (array sizes, template arguments).constexprfunction: can run at compile time when given constant arguments, and also at run time.consteval(C++20) function: must run at compile time.
8. What is the One Definition Rule (ODR), and what does inline mean today?#
Show solution
The ODR says that every function and variable used in a program must have exactly one definition, although some entities (inline functions, templates, classes) may be defined in several translation units as long as all definitions are identical. Today, inline mainly means "this definition may appear in multiple translation units" (for example, functions or C++17 inline variables defined in headers), not "please inline this call"; the optimizer decides that independently.
9. Why are templates usually defined in header files?#
Show solution
A template is instantiated when it is used with specific types, and the compiler needs the full definition at that point. If the definition is in a separate .cpp file, other translation units only see the declaration, and the linker reports undefined references. Alternatives are explicit instantiation for a known set of types, or C++20 modules.
Memory and performance#
10. What is struct padding, and how can you reduce it?#
Show solution
Members are aligned to their natural alignment, so the compiler inserts padding bytes between members and at the end:
#include <iostream>
struct Unordered { char a; double b; char c; }; // 1 + 7 pad + 8 + 1 + 7 pad
struct Ordered { double b; char a; char c; }; // 8 + 1 + 1 + 6 pad
int main() {
std::cout << sizeof(Unordered) << ' ' << sizeof(Ordered) << '\n';
}24 16Ordering members from largest to smallest alignment reduces padding. (Exact sizes are implementation-defined; these are typical for 64-bit platforms.)
11. Why can std::vector beat std::list even for insertions?#
Show solution
std::list has O(1) insertion given an iterator, but finding the position is O(n) pointer chasing, with every node in a separate heap allocation scattered through memory. std::vector stores elements contiguously, so the CPU cache and prefetcher make linear scans and element shifts extremely fast. For small to medium sizes, and for anything that searches first, a vector usually wins. Measure before choosing a list.
12. What is false sharing?#
Show solution
When two threads write to different variables that sit on the same cache line (often 64 bytes), each write invalidates the line in the other core's cache, forcing slow transfers even though no data is actually shared. Fix it by padding or aligning per-thread data to separate cache lines (alignas(64) or std::hardware_destructive_interference_size).
Concurrency and correctness#
13. What is a data race, and how do you prevent one?#
Show solution
A data race is two threads accessing the same memory location concurrently, at least one writing, without synchronization. It is undefined behaviour. Prevent it with a std::mutex (via std::lock_guard/std::scoped_lock), with std::atomic for single variables, or by not sharing mutable data at all (each thread owns its data, and results are combined after join).
14. What is the difference between a mutex and an atomic?#
Show solution
An atomic makes individual operations on one variable indivisible, usually with a single CPU instruction and no blocking. A mutex protects an arbitrary section of code involving any number of variables, but threads waiting for it block. Use atomics for counters and flags, and mutexes for invariants that span several variables.
15. Give examples of undefined behaviour that optimizers exploit.#
Show solution
- Signed overflow: the compiler may assume
x + 1 > xis always true forint x, and remove an "overflow check" written that way. - Null dereference: after
*p, the compiler may assumep != nullptrand delete a later null check. - Out-of-bounds access: a loop that reads
a[i]fori <= non ann-element array may be "optimized" in surprising ways. - Data races: the compiler may keep a shared variable in a register, so another thread's write is never seen.
Because the compiler assumes UB never happens, UB can affect code before and after the faulty line. Sanitizers (-fsanitize=address,undefined,thread) catch many cases.
Next steps#
Practise turning these ideas into code in Coding Challenges.
Finished this lesson?