Modern C++ Features
Common Pitfalls
Identify and avoid common mistakes in modern C++, including dangling captures and use-after-move errors.
Interview: Dangling references in lambda captures, use-after-move undefined behavior, and returning references to temporary objects.
While modern C++ provides powerful features, it also introduces subtle pitfalls. Understanding errors like dangling captures and use-after-move is key to writing safe code.
Dangling Captures
Occurs when a lambda captures stack variables by reference and runs asynchronously after the stack frame is destroyed.
Use-After-Move
Accessing an object after passing it to std::move. This leaves the object in a valid but unspecified state, leading to bugs.
Temporary References
Returning references to temporary local variables. The reference becomes dangling immediately when the function returns.
The Danger of Use-After-Move
Calling std::move casts an object to an rvalue reference, allowing other objects to steal its resources. After a move:
std::string s2 = std::move(s1);
std::cout << s1; // PITFALL: s1's state is unspecified!
While the moved-from object (s1) remains in a valid state (and can be reassigned), its contents are unspecified. Reading its value without re-initializing it leads to undefined behavior.
Code Walkthrough
Illustrates common C++ pitfalls and demonstrates their safe, modern alternatives.
#include <iostream> #include <string> #include <functional>// PITFALL: Returns a reference to a temporary local variable const std::string& getDanglingName() { std::string name = "Temporary"; return name; // Compiler warns: reference to local variable returned }
// SAFE: Returns the object by value, leveraging Return Value Optimization (RVO) std::string getSafeName() { std::string name = "Safe"; return name; }
int main() { // 1. Use-after-move pitfall std::string text = "Initial Content"; std::string target = std::move(text);
// std::cout << text; // DANGEROUS: text is in an unspecified state text = "New Content"; // Re-initialization is safe std::cout << "text: " << text << "\n";
// 2. Dangling capture pitfall std::function<void()> func; { std::string localMsg = "Message"; // func = [&localMsg] { std::cout << localMsg; }; // PITFALL: localMsg will be out of scope func = [localMsg] { std::cout << "Capture: " << localMsg << "\n"; }; // SAFE: Captured by value } func(); // Safe to execute because localMsg was captured by value
return 0; }
Interview-Relevant Information
Q: Why is returning a reference to a local variable dangerous?
Answer: Local variables are allocated on the stack. When the function returns, its stack frame is popped, destroying all local variables. The returned reference points to deallocated memory, resulting in a dangling reference and immediate undefined behavior when read.
Q: What state is a moved-from object left in?
Answer: Standard library specifications guarantee that a moved-from object is left in a valid but unspecified state. This means the object's invariants are maintained, its destructor can run safely, and it can be assigned a new value. However, you should not read its value or assume it contains specific data.
Quick Checklist
Did you avoid reading moved-from variables? Are your async lambdas capturing parameters by value? If yes, your code is free of these common pitfalls.
Use Cases
Securing background task managers from race conditions and dangling references.
Reviewing and optimizing memory access patterns in performance-critical codebases.
Common Mistakes
Capturing stack variables by reference in asynchronous execution blocks, resulting in dangling reference crashes.
Accessing class member properties or variables after they have been moved out of the local scope.