Pointer or reference
int& r = x;int* p = nullptr;TipA reference can't be null or reseated, so a const std::string& parameter needs no null check.
Asked in interviews
AskedWhat is the difference between a pointer and a reference?unique_ptr has one owner, and shared_ptr has several. weak_ptr and T& own nothing.O(1). std::move itself is only a cast.std::vector. Use the heap when an object must outlive its scope or is too big for a frame.int& r = x;int* p = nullptr;TipA reference can't be null or reseated, so a const std::string& parameter needs no null check.
Asked in interviews
AskedWhat is the difference between a pointer and a reference?delete, or a container that reallocated.T make() { T t; return t; }TrapSetting a pointer to null after delete helps only that copy. A returned reference to a local dangles too, though it often passes a small test.
const int* p = &x;Pointer to constint* const q = &x;Const pointerconst int* const r = &x;Const pointer to constTipRead right to left. int* const q says q is a const pointer to int.
Asked in interviews
AskedWhat is the difference between a const pointer and a pointer to const?T t;T* p = new T;delete p;void* m = malloc(n);free(m);TipThe stack is small, often 1 to 8 MB. Deep recursion or a huge local array overflows it.
TrapMixing the pairs is undefined behaviour, and new[] pairs only with delete[].
auto u = std::make_unique<T>(); std::weak_ptr<T> w = s;new with no delete, or a return or throw that skips cleanup.valgrind --leak-check=full ./appTrapAn owning T* looks the same as a borrowed one, so the compiler can't catch a leak or a double delete.
void f(std::unique_ptr<T> p);f(std::move(u));void f(const T& t);TrapTaking unique_ptr& says the function may or may not take ownership. That is the ambiguity smart pointers exist to remove.
Asked in interviews
AskedWhat happens when you pass a unique_ptr by value into a function?TipThe fix is to stop copying it. A function that takes no ownership can use const shared_ptr& or a plain reference.
TrapTwo shared_ptr built from one raw pointer make two control blocks, so the object is deleted twice.
std::weak_ptr<Node> parent;if (auto p = parent.lock()) p->touch();TrapEach object keeps the other's count at one, so neither dies. A stored lambda that captures shared_ is a one-object cycle.
std::ifstream f{path};new member leaks.TrapOnly automatic storage, meaning ordinary locals, gets this. Scope exit destroys the pointer to a new object, not the object.
vector and unique_ptr members, and declare none of the five.T(T&&) = default;The compiler writes one only if you declared none of the other four.TrapA destructor added for one log line stops the implicit move. Every move then silently copies.
std::move doesIt casts to an rvalue reference. It moves no bytes.T(T&& o) noexcept;Copy the handles, then blank the source.std::move cast. A named object copies.Trapstd::move on a const object, or a type with no moves, falls back to a full copy.
T& a = x;T&& b = make();T&& to a named variableint&& r = x; won't compile. static_cast<int&&>(x) forces it, and that is std::move.TrapA named rvalue reference is an lvalue. In a move constructor, wrap its members in std::move again, or they are copied.
T&&std::move(x)T&&std::forward<T>(x)T&& deduced on that same function. An lvalue makes it collapse to U&.TipA forwarding constructor beats the copy constructor for non-const lvalues, so constrain it.
Trapstd::move on a deduced T&& silently empties a variable the caller still owns.
unique_ptr and shared_ptr. Others are valid but unspecified.return w;return std::move(p.first); TipShort strings live inside the object, so a moved-from std::string may keep its characters.
Trapreturn std::move(w); disables elision and usually makes the code slower.
T(T&&) noexcept;If it throws anyway, the program calls std::terminate.TipCopy-and-swap gives the strong guarantee, but it always allocates. For big buffers in a loop, hand-write the reuse path.
TrapWithout noexcept, a copyable type is copied, not moved, when a vector grows.
virtual void draw();void draw() override;virtual double area() const = 0;The class is then abstract.void f(const Base& b);vector<unique_ptr<Base>> v;TipWithout virtual, the static type decides, and your override is skipped.
TrapCopying a derived object into a base variable keeps only the base part. By-value parameters and vector<Base> do this.
TipThe cost that shows in profiles is lost inlining. On a final class, the compiler can devirtualize the call.
virtual ~Base() = default;protected: ~Base() = default;virtual ~Base() = 0;Base::~Base() {}Leave out the body and you get a link error.virtual std::unique_ptr<Base> clone() const = 0;TipA constructor can't be virtual, because it is what installs the vptr. Inside one, a virtual call runs the class being built.
TrapDeleting through a base with no virtual destructor is undefined behaviour. The derived destructor usually never runs.
struct B : virtual A {};TrapThe most derived class initializes the virtual base, so D constructs A itself.
Asked in interviews
AskedHow does virtual inheritance solve the diamond problem?std::vector<T>std::deque<T>std::list<T>O(log n) lookupstd::map<K, V>O(1) lookupstd::unordered_map<K, V>stack, queue and priority_queue are adaptors.m.at(k)m[k]m.contains(k)TipThe four families are sequence, ordered, unordered and adaptors. How they store elements decides what is cheap.
TrapA list insert still costs O(n) to reach the position. A hash table can lose on long string keys.
int a[3];std::array<int, 3> a;std::vector<int> v;v.size()v.capacity()v.reserve(n);TipGrowth is geometric, so push_back is amortised O(1). Capacity never shrinks on its own.
TrapA C array decays to a pointer when you pass it. Indexing past its end is undefined behaviour with no diagnostic.
vectorGrowth invalidates all. erase invalidates from that position on.dequeInvalidates all iterators.list, map or setInvalidates only the erased element.unordered_mapInvalidates iterators. Pointers and references stay valid.it = c.erase(it);Tipstd::sort needs random access, so it won't compile on a list.
TrapUsing an invalidated iterator is undefined behaviour, though it usually appears to work.
template <class T> T twice(T x);std::vector v{1, 2};template <> struct Box<bool> {};template <class T> struct Box<T*> {};TipFunction templates can't be partially specialized. A plain overload is usually the clearer fix.
TrapThe definition must be visible where it is instantiated. That is why template code lives in headers.
template <std::integral T>void f(std::integral auto x);enable_if. A bad substitution removes only that candidate.begin(), not one exact type.TrapSFINAE only recovers failures in the signature. An invalid expression in the function body is a hard error.
const int n = read();constexpr int n = 16;consteval int f();mutable int hits_ = 0;Tipmutable fits a cache or a counter, but guard it if several threads read the object.
TrapA constexpr function runs at compile time only when the result must be a constant. Otherwise it is an ordinary call.
struct Square : Shape<Square> {};static_cast<const D*>(this)->areaImpl(); void f(this auto&& self);TipThere is no vtable, so the compiler can inline the call.
TrapShape<Square> and Shape<Circle> are unrelated types, so they can't share one container.
Asked in interviews
AskedWhat is the Curiously Recurring Template Pattern (CRTP) used for?throw std::runtime_error("bad"); catch (const std::exception& e)class E : public std::runtime_error TipA type outside the std::exception tree usually ends in std::terminate. So does a destructor that throws during unwinding.
TrapThe first matching handler wins, so a base-class handler listed first hides the derived handlers after it.
std::expected<T, E> parse();std::optional<T> find(Id id);TipThrow when there is no return channel, as in constructors, or when the handler is many frames away. A throw is slow, often microseconds.
Asked in interviews
AskedHow do exceptions differ from error codes, and when do you prefer each?[limit](int x) { return x < limit; }[&limit](int x) { return x < limit; }[p = std::move(ptr)]() {}TrapA reference capture doesn't extend the variable's lifetime. A callback that outlives the scope should capture by value.
std::atomic<int> hits{0};hits.fetch_add(1); std::unique_lock lk{m};cv.wait(lk, [&] { return ready; });TipTwo threads on a plain variable with no lock, and at least one writing, is a data race and undefined behaviour.
TrapAnother thread can see two atomics half-updated. An invariant across several variables still needs a mutex.
Asked in interviews
AskedWhat does std::atomic provide, and when do you still need a mutex?void f(std::string_view s);std::string_view v = std::string("x");TrapA view isn't null-terminated in general, so don't pass data() to a C function that expects a terminated string.
Asked in interviews
AskedWhat is std::string_view, and when can it dangle?g++ -E a.cppg++ -S a.cppg++ -c a.cppg++ a.o b.o -o appint add(int, int);int add(int a, int b) { return a + b; }TipA non-inline definition in a header that two .cpp files include gives duplicate symbol errors.
TrapAn undefined reference is a linker error. The name was declared, but a .cpp file or library was left out.
g++ -g -fsanitize=address,undefined a.cppTipCompilers optimize as if undefined behaviour never happens. So the crash lands far from the mistake.
Asked in interviews
AskedWhat are the usual causes of undefined behaviour in C++ code?Nothing matches. Try a shorter word, or clear the search.
With Pro, you can:
Already have Pro? Sign in