C++ interview bank
Questions interviewers actually ask, each answered at the length you would say it aloud, with the topic to reread if you were not sure.
Language core
11 questions · 0 Seen01 What does std::move do, and when does a move actually happen? reveal ▾ hide ▴
std::move is a cast that turns its argument into an xvalue while preserving its type qualifiers. It does not transfer a buffer or change the source by itself. A move happens only if the following initialization, assignment, or call selects an operation that takes an rvalue and that operation actually transfers state. If no usable move overload exists, a copy can still be selected. Also, a named rvalue-reference parameter is an lvalue expression, so a move constructor normally uses std::move on the source members it intends to transfer.
05 How does a built-in array differ from a pointer, and when does it convert to one? reveal ▾ hide ▴
A built-in array is an object containing a fixed number of contiguous elements, and its extent is part of its type. A pointer is a separate scalar object containing an address; its type carries no element count. An array lvalue converts to a pointer to its first element in most value-taking expressions, including ordinary by-value auto deduction. The conversion does not occur for operations such as sizeof, unary address-of on the array, decltype, or binding an array reference. After conversion, the original extent cannot be recovered from the pointer alone.
17 How do you read int (*handler)(int, int), and why are the parentheses necessary? reveal ▾ hide ▴
Start at handler and read outward. The leading star, grouped by parentheses, makes handler a pointer. The following parameter list says the pointed-to entity is a function taking two int arguments, and the initial int is that function’s return type. Function and array postfix operators bind more tightly than prefix star. Without the parentheses, int handler(int, int) declares handler as a function that takes two ints and returns int. A typedef can name either the pointer type or the underlying function type when a public signature becomes hard to scan.
21 What determines a C struct’s size and member offsets? reveal ▾ hide ▴
Members appear in declaration order and later ordinary members have higher addresses, but the implementation may insert padding between members and after the last member to satisfy alignment. The first member starts at the struct address. sizeof includes tail padding so adjacent array elements are correctly aligned; offsetof reports a particular member offset, and alignof reports the type’s alignment requirement. Exact results depend on the target ABI, compiler options, member types, and packing settings. I verify required layouts with static assertions on every supported target instead of treating one x86-64 observation as portable C.
25 How do class and struct differ in C++, and how do you choose between them? reveal ▾ hide ▴
Both keywords define class types and support data members, member functions, constructors, templates, and inheritance. The language differences are their defaults: class makes members and bases private, while struct makes them public. I use struct for an open record whose fields callers may combine directly. I use class when changes must pass through operations that preserve an invariant. That convention communicates intent but does not change the formal type rules. Aggregate status is separate, so changing class to struct does not automatically make a type an aggregate.
29 What happens from a throw expression until a matching catch begins? reveal ▾ hide ▴
The throw operand initializes a separate exception object, and the runtime searches dynamically enclosing handlers in source order. A compatible type, accessible unambiguous base, certain pointer conversion, or catch-all can match. As the search leaves scopes, fully constructed automatic objects are destroyed in reverse construction order. This is stack unwinding and is why resources need RAII owners. Once a handler matches, its parameter is initialized and its body runs. If no handler matches, or an exception crosses a noexcept boundary, the program ultimately calls std::terminate.
37 How does a namespace differ from a class and a C++20 module? reveal ▾ hide ▴
A namespace is an open declarative region for organizing names and participating in lookup. It has no instances, inheritance, or public and private member access, and separate files can extend it. A class defines a type whose objects hold state and whose access specifiers enforce member access rules. A module controls declaration visibility and export across module units and can reduce dependence on textual inclusion. Modules and namespaces are complementary: exported declarations still belong to namespaces. I choose a namespace for ownership of names, a class for an object contract, and a module for a distribution and visibility boundary.
41 How do you choose between a member and non-member operator overload? reveal ▾ hide ▴
Start with the language constraints: assignment, subscript, function call, and arrow operators must be members. A mutating operation such as += also naturally belongs to the left object. For symmetric binary operations, I usually use a non-member, often a hidden friend, because both operands then participate in conversion matching the same way. Stream insertion is non-member because std::ostream is the left operand and I cannot add members to it. friend is only an access choice, not what makes a function non-member. I test both operand orders whenever the domain claims symmetry.
49 What must match for a derived member to override a base virtual function? reveal ▾ hide ▴
The parameter list and the member function’s cv- and reference-qualifiers must satisfy the override rules. A trailing const or an & qualifier is therefore not cosmetic. Return types are normally identical, with a limited covariance rule for pointers or references to class types. Access level does not determine overriding, so a private base virtual can still be overridden. I put override on every intended derived implementation: it does not create an override, but makes the compiler reject a mismatch. I also enable hidden-virtual warnings because same-named overloads can disappear from lookup without being overrides.
56 How do const int * and int *const differ? reveal ▾ hide ▴
In const int *view, const qualifies the pointed-to int. I may assign view another address, but I may not modify the target through view. In int *const fixed, const qualifies the pointer object: fixed keeps one address, while *fixed remains writable. const int *const combines both restrictions. Adding pointee constness when converting int * to const int * is normally safe. Casting it away does not make an object defined as const writable; modifying that object still has undefined behavior. I read declarations outward from the variable name and review each indirection level separately.
68 What contract does a C++ reference express compared with a pointer? reveal ▾ hide ▴
A reference normally expresses a required object or function that will not be reseated through that name. In a well-defined program it is initialized to a valid target, and assignment changes the target rather than the binding. A pointer is a separate value that can represent null and can be redirected, so it fits optional or navigable relationships. Neither raw form owns the target, and both can dangle. I choose between them from nullability and reseating first, then document the owner, invalidating operations, and how long the caller may retain the access.
Resource ownership
2 questions · 0 Seen02 What may code safely do with a moved-from object? reveal ▾ hide ▴
For standard-library types, unless a component gives a stronger postcondition, the object is valid but its value is unspecified. It can be destroyed or assigned a new value, and operations without violated preconditions may be called. That does not make indexing a moved-from vector safe, because its size is unknown. A particular type can document a stronger state: a moved-from unique_ptr is empty. For custom types, the class must define and preserve its own invariant, especially when a pointer is paired with a size or ownership flag.
45 What does RAII guarantee, and why is it not synonymous with stack allocation? reveal ▾ hide ▴
RAII binds release responsibility to the lifetime of an owning object. After acquisition succeeds, the resource immediately enters an object whose destructor performs the paired release, so normal return, early return, and exception unwinding use the same cleanup path. Stack allocation is only one way to manage the owner. An RAII object can be a data member, an element of a container, or dynamically allocated under a smart pointer. The real contract is ownership plus deterministic destruction when that owner’s lifetime ends. RAII does not promise cleanup after abort, power loss, or every process-termination API.
Standard library
2 questions · 0 Seen03 Why does noexcept matter for a move constructor used by containers? reveal ▾ hide ▴
During reallocation, a container may need to preserve its original elements if constructing the new storage fails. A non-throwing move provides a safe transfer path. If moving may throw and copying is available, facilities such as std::move_if_noexcept can select the const-reference path so the source stays unchanged on failure. If the type is move-only, the container may have to use the throwing move and offer a weaker guarantee for that operation. The noexcept annotation must be truthful; an escaping exception from a noexcept move calls std::terminate.
20 What makes a correct qsort comparator, and why is subtraction risky? reveal ▾ hide ▴
qsort passes pointers to two elements as const void*. The comparator converts them to pointers to the actual element type and returns a negative value, zero, or a positive value for less than, equivalent, or greater than. It must provide a consistent ordering and should not mutate elements or depend on invocation order. Returning left->key - right->key is risky because signed integer overflow is undefined behavior. Build the sign from relational comparisons or explicit branches instead. Equal results do not imply stable output order, because qsort does not promise stability.
Class design
2 questions · 0 Seen04 How do the Rule of Zero and Rule of Five guide a movable class design? reveal ▾ hide ▴
The Rule of Zero is the preferred design: put ownership in members such as vector, string, or unique_ptr and let their special members compose. This removes duplicated lifetime code and usually gives correct conditional noexcept behavior. The Rule of Five is a warning for classes that customize destruction, copying, or moving: treat the destructor, copy constructor, copy assignment, move constructor, and move assignment as one semantic set. Decide explicitly whether each is defaulted, deleted, or custom, then test source and target invariants, self-assignment, base classes, and destruction.
27 What does the Rule of Zero mean for class design? reveal ▾ hide ▴
The Rule of Zero says that a class should avoid declaring its own destructor, copy operations, or move operations when its members already model ownership correctly. Members such as string, vector, and smart pointers compose their behavior into the outer class. That removes duplicated lifetime code and lets the type system delete copying when a member is noncopyable. I still verify the resulting semantics: memberwise copying must match the domain, references and non-owning views need lifetime rules, and a user-declared constructor or destructor can change which special member functions are generated.
API design
4 questions · 0 Seen06 How can a C++ function receive an array without losing its extent? reveal ▾ hide ▴
Array-looking parameters such as int values[8] are adjusted to int*, so that spelling does not preserve or enforce eight elements. A reference parameter such as const int (&values)[8] preserves an exact extent. A template const T (&values)[N] can deduce both element type and extent. For most application interfaces, std::span
28 What does trailing const on a member function guarantee, and what does it not guarantee? reveal ▾ hide ▴
A trailing const qualifies the current object for that call. The function can be invoked through a const object or const reference, and ordinary member access cannot modify non-mutable data through this. It does not make returned references outlive the object, guarantee thread safety, or make the function a compile-time computation. Mutable members and objects reached through pointers can still change, so const is not deep immutability. I mark observers const, test them through const references, and document the lifetime of any reference, pointer, span, or iterator they return.
59 When should a C API use a pointer-to-pointer output parameter? reveal ▾ hide ▴
A single T * lets a function modify a T object. To replace the T * value stored by the caller, the function needs access to that pointer object, commonly through T **out and an &pointer argument. The extra level does not by itself mean allocation or ownership transfer. The API must say whether the result is borrowed or owning, who releases it, and what happens to the old pointer. I prefer failure-atomic output: validate and prepare first, then assign *out once on success so failure preserves the caller’s old value and no partially initialized pointer escapes.
71 How do you review whether a function can safely return a reference? reveal ▾ hide ▴
I identify the exact object denoted by every return path, then name its owner. An automatic local, a by-value parameter, or a temporary computed in the return expression cannot outlive the call. A member or container element can outlive it, but only while the owning object survives and no operation invalidates that element. I also check whether const and mutable overloads preserve access rules. Tests should cover owner destruction, vector reallocation, erasure, and temporary receivers. If callers cannot state and enforce the lifetime boundary simply, returning an owning value is safer.
Memory layout
1 question · 0 Seen07 Why can an int[Rows][Cols] array not be passed as int**? reveal ▾ hide ▴
A two-dimensional built-in array is an array of row arrays. After the outer array converts, the result points to one complete row, so int grid[2][3] becomes int ()[3]. The column extent is needed for pointer arithmetic to reach the next row. By contrast, int** points to an int object and commonly represents a separate table of row pointers. The built-in array contains element rows directly and has no row-pointer objects. A cast cannot create that missing indirection; dereferencing through int** would interpret integer storage as pointers and produces undefined behavior.
Library design
2 questions · 0 Seen08 When should code use a built-in array, std::array, std::span, or std::vector? reveal ▾ hide ▴
Use a built-in array when language-level layout or C interoperability requires it, or for very local fixed storage where it never crosses an interface. Prefer std::array<T, N> when fixed-size storage needs ordinary value semantics such as assignment and return by value. Use std::span
40 What problems do unnamed and inline namespaces solve, and what do they not guarantee? reveal ▾ hide ▴
An unnamed namespace gives its names internal linkage within one translation unit, which suits helpers and implementation types in a source file. In a header, however, each translation unit gets separate entities, so state can split unexpectedly. An inline namespace exposes its members through the enclosing namespace’s lookup and is commonly used to select a default API version while retaining explicit version names. It does not mean function inlining, and switching versions does not guarantee ABI compatibility. I use unnamed namespaces for source-local ownership and inline namespaces only with an explicit versioning and binary-compatibility policy.
Compilation model
1 question · 0 Seen09 How do a declaration, a definition, and linking differ in C? reveal ▾ hide ▴
A declaration introduces a name and its type so the compiler can check later uses. A definition is the declaration that also supplies a function body or storage for an object. A header normally carries declarations shared by translation units, while one source file supplies each required external definition. Compilation checks each translation unit separately. Linking then resolves external references between object files and libraries. Compatible repeated declarations are expected; missing definitions cause unresolved references, while multiple external definitions of the same object or function violate the program contract and commonly produce a linker error.
Types and conversions
1 question · 0 Seen10 Why can comparing a negative int with size_t give an unexpected result? reveal ▾ hide ▴
size_t is an unsigned integer type. Before a mixed arithmetic comparison, integer promotions and the usual arithmetic conversions choose a common type. Depending on the types and their ranks, the negative int can convert to an unsigned type, where it becomes a large value modulo that type’s range. The comparison then operates on those converted values, not on the mathematical integers suggested by the source. Keep one signedness within a value domain, check a signed error result before conversion, and prove range validity before converting it to size_t.
Correctness
1 question · 0 Seen11 What is undefined behavior, and why can optimization change how a bug appears? reveal ▾ hide ▴
Undefined behavior is behavior for which the C standard imposes no requirements. Examples include signed integer overflow, out-of-bounds access, and conflicting unsequenced modifications. It is not a portable error value or a guaranteed crash. An optimizer may assume that a valid execution never reaches undefined behavior and simplify code using that premise, so a defect can disappear, move, or produce a different result under optimization. Prevent it with explicit contracts and bounds, then use strict warnings, focused tests, AddressSanitizer, and UndefinedBehaviorSanitizer to catch violations on exercised paths.
Functions
1 question · 0 Seen12 Does C pass arguments by reference when a function receives a pointer? reveal ▾ hide ▴
No. C always passes each argument by value. For an int parameter, the callee receives a copy of the integer; for an int* parameter, it receives a copy of the pointer value. Dereferencing that copied pointer can modify the caller’s pointed-to object, which creates reference-like behavior, but assigning a different address to the local pointer does not change the caller’s pointer. To replace the caller’s pointer, pass its address as a pointer to pointer. The interface must also define whether null is accepted and how long the pointed-to object remains valid.
Standard I/O
2 questions · 0 Seen13 How should a C input loop distinguish end of file from an I/O error, and why does fgetc return int? reveal ▾ hide ▴
The read operation must drive the loop: test the return from fgetc, fgets, or fread instead of checking feof first. After EOF, NULL, or a short fread count, feof reports that no more input was available, while ferror reports an I/O failure. These indicators describe a read that already happened. fgetc returns int so it can represent every unsigned-char value plus the distinct EOF sentinel. Store its result in int, compare it with EOF, and only then convert or pass the successful character value onward.
14 What do fread and fwrite return, and how should code handle a short count? reveal ▾ hide ▴
Both functions return the number of complete elements transferred, not the number of bytes, unless the element size argument is one. A short fread count can mean ordinary end of file or a read error, so code processes the elements received and then queries feof and ferror when the count is below the request. A short fwrite count means the output is incomplete. A byte-oriented write helper advances its pointer by the accepted count and handles the remainder or fails explicitly. Final fflush and fclose errors must still affect the overall result.
Stream state
1 question · 0 Seen15 What sequencing rules apply when switching between input and output on an update stream? reveal ▾ hide ▴
An update mode such as r+, w+, or a+ permits both directions, but it does not make arbitrary switching valid. Before input directly follows output, code must successfully call fflush or a file-positioning function. Before output directly follows input, it normally needs a successful positioning call; the exception is when the input operation encountered end of file. An explicit checked fseek at a phase boundary makes both the intended position and direction transition visible. When phases are independent, closing and reopening with narrower modes can produce a simpler contract.
Data formats
1 question · 0 Seen16 Why is writing a structure with fwrite usually not a portable file format? reveal ▾ hide ▴
fwrite writes the structure’s object representation for the current implementation. That representation can contain padding bytes, native byte order, implementation-specific type widths, and floating-point encodings. Padding can also contain indeterminate process data that should not be disclosed. A stable format defines a magic value, version, field widths, byte order, length bounds, and validation rules independently of the in-memory type. Encode and decode each field, reject truncated or inconsistent input before allocation, and use raw layouts only for deliberately local files constrained to one ABI and controlled lifetime.
Type safety
1 question · 0 Seen18 Why does casting an incompatible function pointer not make the call safe? reveal ▾ hide ▴
A cast changes the pointer expression’s static type, but it does not change the target function’s actual parameter and return conventions. At the call, C requires the function type used by the call to be compatible with the definition of the target. Otherwise behavior is undefined: arguments or results may be interpreted incorrectly, and a successful build proves nothing. C does allow conversion between function-pointer types and guarantees equality after converting back to the original type. That round-trip guarantee is about preserving the pointer value, not permission to call through the intermediate type.
Callbacks and ownership
1 question · 0 Seen19 What contract must accompany a callback that takes a void* context? reveal ▾ hide ▴
The signature alone does not say what object context points to, who owns it, or how long it remains valid. The API must state whether invocation is synchronous or retained, when unregistration completes, which thread calls, whether calls can overlap or reenter, and who releases storage. The callback must convert context back to the same object-pointer type that originally produced it. A stack address works for a synchronous traversal but dangles if retained beyond the call. For persistent registration, the owner must keep storage alive until no in-progress or future callback can access it.
Type design
2 questions · 0 Seen22 How do you design a safe tagged union in C? reveal ▾ hide ▴
Place an enum discriminant and a union payload in one struct, then define an invariant mapping each tag to exactly one member. Constructor and mutator functions update them together. Every reader, copier, serializer, and destructor switches on the tag before touching the payload and rejects unknown values at external boundaries. If members own resources, switching alternatives first releases the old member, and cloning performs a deep copy where ownership requires it. Internal switches can omit default under exhaustive-enum warnings, while boundary code validates raw integers before calling that internal logic.
33 When is public inheritance the right relationship in C++? reveal ▾ hide ▴
Public inheritance is appropriate when every derived object can stand in for a base object without surprising code that follows the base contract. The derived class must accept the base’s valid inputs, preserve its postconditions and invariants, and avoid exposing operations that become meaningless. Reusing a few members is not enough. I test the decision by writing callers against Base references and running the same contract tests over each derived type. If the relationship is really has-a, or the derived type must restrict base behavior, I prefer composition or a separate interface.
Memory ownership
1 question · 0 Seen23 What happens when you assign a struct that contains a pointer? reveal ▾ hide ▴
Struct assignment copies every member value. An array member is copied as part of the struct, but a pointer member contributes only its address, so source and destination then refer to the same pointed-to object. That is safe for an explicitly borrowed immutable object with a sufficient lifetime. It is dangerous for ownership: two apparent owners may double-free, or one release may leave the other dangling. An owning type needs documented initialization, clone, transfer, and destruction operations. Its clone allocates independent storage and must clean up partial work while leaving the source valid if allocation fails.
Portability
1 question · 0 Seen24 Why are C bit fields a poor default for network packets and hardware registers? reveal ▾ hide ▴
A bit-field width limits the value bits available to a member, but the implementation controls important layout details such as allocation units, ordering within a unit, and whether fields cross unit boundaries. Packing and byte order add further target-specific assumptions. You also cannot take a bit field’s address, and an ordinary read-modify-write is not automatically atomic. For a portable external format, I load bytes into an unsigned integer of known width, apply explicit masks and shifts, and handle byte order separately. A bit-field mapping is acceptable only under a documented, tested compiler and ABI contract.
Object lifetime
4 questions · 0 Seen26 In what order does a C++ constructor initialize an object, and why does that order matter? reveal ▾ hide ▴
For a complete object, virtual bases initialize first, then direct bases, then non-static data members in declaration order, and finally the constructor body runs. The written order of the member initializer list cannot change that sequence. This matters when one member’s initializer reads another member: the dependency must already be initialized according to declaration order. I keep declarations and initializers in matching order and enable reorder warnings. Destruction reverses construction. If construction throws, completed bases and members are destroyed, but the unfinished outer object’s destructor does not run.
35 What destructor policy should a C++ base class use? reveal ▾ hide ▴
The policy follows how the interface permits destruction. If clients may own a derived object through Base* or unique_ptr
50 What happens to virtual dispatch inside constructors and destructors? reveal ▾ hide ▴
While a Base constructor runs for a Derived object, virtual calls on that object dispatch no further than Base and its already constructed bases. The Derived lifetime has not begun. Destruction mirrors this: once execution reaches Base’s destructor, the Derived portion has ended its lifetime, so dispatch does not return to it. A virtual call that reaches a pure virtual function during construction or destruction has undefined behavior. I avoid overridable initialization hooks in constructors; a factory can finish construction first, then call an explicit virtual step on the complete object.
69 When does binding a reference extend a temporary object’s lifetime, and where does that rule stop? reveal ▾ hide ▴
Directly binding a local const lvalue reference or rvalue reference to certain temporary expressions can extend the temporary to the reference’s lifetime. The extension is not transitive. A temporary bound to a reference parameter normally lasts only until the end of the full expression containing the call, so storing or returning that parameter does not keep the object alive. A temporary created in a return expression also cannot support a returned reference. I trace the exact initialization expression and ultimate owner; when the rule becomes subtle, returning or storing an owning value is usually the clearer interface.
Exception safety
1 question · 0 Seen30 How do the basic, strong, and no-throw exception guarantees differ? reveal ▾ hide ▴
The basic guarantee says failure leaks no resources and leaves every affected object valid, though values may have changed. The strong guarantee adds that observable state is unchanged, as if the operation never happened. The no-throw guarantee says the operation does not fail by propagating an exception. I prove these against each potentially throwing step, not by counting try blocks. RAII establishes resource cleanup, while prepare-then-commit or copy-and-swap can establish the strong guarantee when the final commit cannot throw. The documented scope must say which objects or external transaction the guarantee covers.
API contracts
1 question · 0 Seen31 What does noexcept promise, and why can an incorrect annotation be dangerous? reveal ▾ hide ▴
noexcept promises that an exception will not leave the function. It does not prevent code in the body from throwing; if an exception does cross that boundary, std::terminate is called and an outer catch cannot recover it. The annotation therefore changes failure semantics rather than merely suggesting optimization. It also participates in type properties and can influence whether generic code such as std::move_if_noexcept selects copying or moving. I derive the specification from base and member operations, callbacks, allocation, and logging, then verify generic cases with nothrow type traits instead of adding noexcept mechanically.
Exception design
1 question · 0 Seen32 How should C++ code catch and rethrow exceptions without losing their dynamic type? reveal ▾ hide ▴
Throw complete objects by value and normally catch polymorphic exceptions by const reference. A catch parameter declared by value creates a base object when the handler type is a base, slicing off derived state. Inside a handler, bare throw; rethrows the currently handled exception object and preserves its dynamic type. Writing throw error; starts a new throw from the parameter expression and can slice again. I catch only where the layer can recover, add policy, or translate types. When translating, I copy stable context into a domain exception or structured cause instead of depending on what() text parsing.
Object model
2 questions · 0 Seen34 What is object slicing, and where do you look for it in an API? reveal ▾ hide ▴
Object slicing happens when a derived object initializes or assigns a base object by value. Only the base subobject is copied, so derived state and the original dynamic type disappear. Virtual functions cannot recover what was never stored. I search for polymorphic bases used as parameters, return values, catch parameters, data members, and container element types. Non-owning APIs normally take Base& or const Base&. Heterogeneous owners commonly store unique_ptr
48 How do static type and dynamic type participate in a virtual function call? reveal ▾ hide ▴
The static type controls name lookup, access checking, overload resolution, and default arguments. If that process selects a virtual member and the call is not explicitly qualified, the complete object’s dynamic type then selects the final overrider. For a Derived object observed through Base&, Base defines which signature is called, while Derived can supply the body. Non-virtual members remain selected from Base. I test this distinction through a base reference, because a direct call on Derived can hide a signature mismatch or name-hiding bug that production code will expose.
Multiple inheritance
1 question · 0 Seen36 How does virtual inheritance change a diamond hierarchy? reveal ▾ hide ▴
Without virtual inheritance, each path through a diamond contributes its own common-base subobject. State, member lookup, and conversion to that common base can therefore be duplicated or ambiguous. When both intermediate classes virtually inherit the common base, the most-derived object contains one shared virtual-base subobject. The most-derived constructor, not an arbitrary intermediate constructor, initializes it. I use this only when both paths represent one shared identity. If each role should own separate base state, ordinary multiple inheritance is correct. I also avoid depending on compiler-specific offsets or hidden-table layouts.
Name lookup
1 question · 0 Seen38 How do a using-declaration and a using-directive affect name lookup? reveal ▾ hide ▴
A using-declaration names specific declarations, such as using std::string, and makes those declarations available in its scope. That keeps the imported name and its owner visible in one line. A using-directive, such as using namespace std, makes unqualified lookup consider names from the nominated namespace without declaring local copies. Its candidate set can change as headers or the namespace evolve, so it can introduce distant ambiguities. I avoid directives in headers, qualify public signatures, and use narrow block-scope declarations when repetition obscures the code. Neither form changes an entity’s identity or linkage.
Generic programming
1 question · 0 Seen39 What is argument-dependent lookup, and why does generic C++ code rely on it? reveal ▾ hide ▴
For an unqualified function call, argument-dependent lookup adds candidates from namespaces and classes associated with the argument types. That lets an operation placed beside a user-defined type participate without a global using-directive. Operators, hidden friends, and protocols such as the classic swap pattern use this behavior. Generic swap code first introduces std::swap as a fallback and then calls swap unqualified, allowing ADL to select a type-specific overload. I separate ordinary lookup from ADL when diagnosing ambiguity. I also avoid qualifying a deliberate customization call, because qualification disables this candidate-adding step, while ordinary business calls can often stay explicitly qualified.
Operator contracts
1 question · 0 Seen42 What return types do arithmetic, compound assignment, increment, subscript, and stream operators normally use? reveal ▾ hide ▴
Binary arithmetic normally returns a new T by value, while compound assignment changes the left operand and returns T& so chaining keeps operating on that object. Prefix increment commonly returns T& after mutation; postfix increment takes a dummy int and returns the old value by value. A container-like subscript usually has T& and const T& overloads for writable and read-only objects. Stream insertion returns std::ostream& to preserve the original stream and permit chaining. These are semantic conventions rather than universal syntax rules, so I verify each type’s stated contract and the lifetime of every returned reference.
Comparisons
1 question · 0 Seen43 What does a defaulted three-way comparison generate, and when is it the wrong choice? reveal ▾ hide ▴
A defaulted operator<=> compares base subobjects and non-static data members in declaration order. Relational operators can use it through rewritten candidates. If the class declares no member or friend named operator==, the defaulted three-way comparison also causes a matching defaulted equality operator to be declared. Its result category is derived from subobject comparisons, so a floating member can produce partial ordering. Defaulting is wrong when stored fields do not all belong to value identity, such as caches, debug counters, or surrogate keys. I define the semantic fields first, then test equality, ordering equivalence, NaN behavior, and transitivity.
Generated code review
1 question · 0 Seen44 How do you review an AI-generated set of operator overloads? reveal ▾ hide ▴
I begin with a contract table: operand types, mutation, result category, equality fields, boundaries, and failure behavior. Then I ask the compiler which member, non-member, built-in, and rewritten candidates exist for representative expressions. Tests cover both operand orders, const objects, chained assignment, invalid conversions, and returned-reference identity. Comparisons need law-based cases rather than six copied functions; floating values add NaN and signed zero. For subscripts I verify writable and const overloads plus the bounds policy. I reject overloaded && or || when correctness depends on short-circuiting, and I inspect every implicit conversion added merely to silence a diagnostic.
Construction and destruction
1 question · 0 Seen46 How should an RAII class handle a resource when its constructor can fail after acquisition? reveal ▾ hide ▴
The complete object’s destructor does not run when its constructor throws, so storing a raw handle in a plain integer or pointer member is unsafe if later initialization can fail. Already-constructed base and member subobjects are destroyed in reverse order. I therefore put the handle in a dedicated RAII member that acquires or adopts it before later fallible work. Another option is a factory that first creates a temporary owner, completes configuration, and only then moves the owner into the returned object. Acquisition failure should leave no apparently usable object unless an invalid state is an explicit part of the type’s contract.
Failure contracts
1 question · 0 Seen47 How do you design RAII cleanup when closing or committing can fail? reveal ▾ hide ▴
I separate reportable completion from fallback destruction. A normal close, finish, or commit method returns status or throws while the caller can still apply policy; after success it marks the object empty or complete. The destructor handles objects that were not explicitly completed, but its cleanup path does not let exceptions escape. Transaction guards commonly roll back by default rather than commit, because scope exit does not prove the operation succeeded. If rollback itself can fail, the destructor can record a best-effort diagnostic or invalidate the connection, while an outer recovery mechanism verifies external state. RAII cannot make an inherently fallible operation infallible.
Interface design
1 question · 0 Seen51 How do pure virtual functions and virtual destructors shape an interface class? reveal ▾ hide ▴
A pure virtual function declares a required operation with = 0 and keeps the class abstract until a concrete final overrider exists. It may still have an out-of-class definition that an override calls with explicit qualification. A pure virtual destructor also needs a definition because base destruction still runs. Destruction policy follows ownership: an interface deleted through Base* needs a public virtual destructor; an interface that forbids such deletion can use a protected non-virtual one. I keep data out of narrow interfaces, state preconditions explicitly, and test every implementation through the base contract.
Memory model
1 question · 0 Seen52 How do storage duration, object lifetime, and ownership differ in C? reveal ▾ hide ▴
Storage duration is a language category that says how long storage is reserved: static, thread, automatic, or allocated. Lifetime is the execution interval during which a particular object exists in that storage and may be accessed. Ownership is an API convention identifying who must end an allocated lifetime with the matching release operation. Copying a pointer creates an alias, not another allocation or another safe owner. I document whether each pointer is owning or borrowed, carry its accessible length, and ensure no borrow crosses free or a successful realloc. “Stack versus heap” alone does not express those obligations.
Allocation APIs
1 question · 0 Seen53 What is the failure-safe way to use realloc? reveal ▾ hide ▴
I first reject zero under the API’s policy and prove the requested byte count cannot overflow size_t. I call realloc into a temporary pointer, leaving the owning pointer unchanged. A null result for a valid nonzero request means the old object and contents remain valid, so the caller can continue or free them. A non-null result is the only new base address; the old object’s lifetime ended even if the address compares numerically equal. I then initialize added bytes and commit pointer, length, and capacity together. Every interior pointer is recomputed from the returned base.
Bounds and security
1 question · 0 Seen54 How do you prevent an allocation-size overflow in C? reveal ▾ hide ▴
The check must happen before multiplication because size_t arithmetic wraps before malloc sees the request. For count elements I reject count greater than SIZE_MAX divided by sizeof *pointer, then compute the product. Multidimensional sizes need one checked step per multiplication, with zero divisors handled by the API’s empty-input policy. C23 calloc rejects a count-times-size product that would wrap, but it cannot enforce a business maximum. I still validate external counts and distinguish invalid size from allocation failure when callers need different recovery. A non-null result is never a substitute for this proof or for later bounds checks.
Failure handling
1 question · 0 Seen55 How do you review cleanup in a C function that acquires several resources? reveal ▾ hide ▴
I list acquisitions in order and inspect every return, goto, and transfer point. At each exit, every resource still owned by the function must release exactly once, while resources never acquired must remain safe to ignore. Initializing handles to a documented empty state makes one reverse-order cleanup block practical because free accepts NULL. Ownership transfer needs an explicit commit point: before it, the function cleans up; after it, the recipient does. I then inject failure at each acquisition in turn and run the matrix under sanitizers. Fallible completion such as flush or commit should be reported separately from fallback release.
Bounds and lifetime
2 questions · 0 Seen57 When are pointer arithmetic, subtraction, and ordering valid in C? reveal ▾ hide ▴
Pointer addition and subtraction are defined around one array object. Starting from an element pointer, the result may designate another element or exactly one past the array; the one-past value is an end sentinel and cannot be dereferenced. Subtracting or ordering two pointers requires them to belong to that same array range. The difference is measured in elements, not bytes. Two separately declared objects do not become an array merely because their addresses are adjacent. I prefer half-open ranges, carry the end or count explicitly, and prove every dereference occurs before the one-past position.
58 Why does a non-null check not prove that a C pointer is safe to dereference? reveal ▾ hide ▴
Null is only one unusable pointer state. A non-null value can be uninitialized, one past an array, outside its range, misaligned for the target type, or dangling because the object was returned from a local scope, freed, or replaced by realloc. A cast can hide a type diagnostic but cannot restore those facts. Before dereferencing, I trace the pointer to a live object, prove the requested element lies in its accessible range, confirm the target type and mutability, and check any documented nullability rule. Sanitizers help test paths, but they do not establish the whole contract.
Translation model
1 question · 0 Seen60 What does the C preprocessor produce, and what does it not understand? reveal ▾ hide ▴
The preprocessor handles inclusion, conditionals, directives, and macro replacement before ordinary compilation. For one source file, that work contributes to the translation unit the compiler analyzes. It operates on preprocessing tokens and whitespace, not typed C values, object lifetimes, or runtime control flow. That is why a preprocessor condition cannot use sizeof and why a macro accepts token sequences that later fail type checking. When debugging, I inspect a small preprocessed result with gcc -E -P, then return to compiler diagnostics for declarations, types, and behavior.
Macro design
1 question · 0 Seen61 When should a function-like macro be replaced with an inline function? reveal ▾ hide ▴
I prefer a static inline function whenever the job is value computation with a stable type. A function evaluates each argument once, participates in type checking, obeys scope, and is easier for debuggers and tools to follow. Parenthesizing a macro fixes precedence but cannot fix repeated evaluation such as MAX(index++, limit). A macro remains justified when it must stringify source tokens, paste tokens, attach FILE and LINE, or remove code through conditional compilation. I keep that layer thin and document any restriction against side-effecting arguments.
Build configuration
1 question · 0 Seen62 How do #ifdef and #if differ for a feature switch? reveal ▾ hide ▴
#ifdef FEATURE asks whether the macro exists, so it is true after #define FEATURE 0. #if FEATURE expands the value and evaluates zero as false; an undefined remaining identifier also becomes zero in that expression. I use #ifndef only to supply a default, then validate that the value is in the allowed set and use #if for the actual branch. When omission has a separate meaning, I check defined(FEATURE) explicitly. Finally, I build and test every supported combination because excluded branches receive no normal compiler checking in that build.
Macro expansion
1 question · 0 Seen63 Why do stringification and token pasting sometimes need two macro layers? reveal ▾ hide ▴
A macro argument normally expands before substitution into the replacement list. The exception is a parameter adjacent to # or ##: that operator receives the unexpanded argument tokens on that pass. Therefore RAW(VERSION), where RAW(x) is #x, produces “VERSION” rather than the macro’s value. An outer STRINGIFY(x) that calls RAW(x) gives the argument one expansion step first. Token pasting uses the same reason for an indirection layer when inputs must expand before joining. I verify the result with preprocessor output because plausible-looking source can hide the wrong expansion stage.
Memory and ownership
1 question · 0 Seen64 How do you choose among unique_ptr, shared_ptr, weak_ptr, and a raw borrow? reveal ▾ hide ▴
I start from the ownership graph, not from convenience. One natural final releaser means unique_ptr, which is move-only and should be the default owning form. Independent parties that must each extend lifetime justify shared_ptr. A relationship that may expire but must not keep the target alive uses weak_ptr and obtains a temporary strong owner with lock. Code that only accesses an object during a documented caller-owned lifetime takes T& or T*. Function parameters and return types should state transfer, co-ownership, observation, or borrowing explicitly rather than spreading shared_ptr through every layer.
Reference counting
1 question · 0 Seen65 How does a shared_ptr control block work, and why can reference cycles leak? reveal ▾ hide ▴
Copies of a shared_ptr join one control block, which tracks strong owners and weak observers together with destruction information. The last strong owner destroys the managed object; the block itself may remain while weak_ptr objects exist. Counting cannot determine reachability from application roots. If two otherwise unreachable objects hold strong pointers to each other, each keeps the other’s strong count above zero, so neither destructor runs. I identify the non-owning direction in the domain model and represent it with weak_ptr, then test by releasing every external strong owner and observing both destruction and failed weak upgrades.
Ownership identity
1 question · 0 SeenConcurrency
1 question · 0 Seen67 What thread-safety guarantees does shared_ptr provide? reveal ▾ hide ▴
Different shared_ptr objects that share a control block can be copied, moved, reset, or destroyed concurrently without corrupting that block. This guarantee does not make the managed T thread-safe, and two threads still cannot mutate the same ordinary shared_ptr object without synchronization. The pointee needs its own locking or immutable design. When a shared pointer value itself must be published or replaced atomically, C++23 provides atomic<shared_ptr
Generic code
1 question · 0 Seen70 Why is a named rvalue-reference parameter an lvalue, and when should code use std::move or std::forward? reveal ▾ hide ▴
Value category belongs to an expression. An expression consisting of a variable’s name is an lvalue even when that variable has type T&&, so passing the name alone selects an lvalue path. std::move casts it to an xvalue and is appropriate when the code deliberately permits its resources to be reused. In a transparent template wrapper, a deduced T&& may be a forwarding reference: T can deduce as an lvalue reference, and reference collapsing preserves that category. std::forward
No questions match this filter.