10 Hidden C++ Performance Traps Every Developer Must Avoid
This article reveals ten common C++ performance pitfalls—from costly abstractions and virtual functions to hidden copies, destructors, and misuse of smart pointers—while also showcasing practical language features and optimization techniques that can dramatically improve runtime efficiency in production code.
Author: 尚晋的思考
If you ask C++ engineers why they love C++, "control" is always at the top of the list. Unlike garbage‑collected languages, C++ gives you complete control over the generated assembly, making performance tuning a daily habit.
Performance Traps
The traps are divided into two categories: "Costly Abstractions" and "Fighting the Compiler".
Costly Abstractions
C++ "zero‑cost abstraction" is often misunderstood; using features like templates incurs compile‑time costs, and many abstractions have hidden runtime overhead.
Virtual functions add an extra indirection, can break CPU pipelines, and prevent inlining.
Implicit copies (e.g., member‑initialization constructors) can cause unnecessary copies; using move semantics or pass‑by‑value with std::move can avoid them.
Implicit destructors may invoke costly cleanup; making destructors = default for trivial types avoids extra work.
Misusing std::shared_ptr adds atomic reference‑count overhead; prefer std::unique_ptr or raw pointers when ownership is clear.
Type erasure with std::function or std::any incurs heap allocations and virtual calls.
Using std::optional can hinder NRVO and increase code size.
Improper use of std::async without a launch policy may turn asynchronous calls into synchronous ones.
Tail‑recursive functions on std::string cannot be optimized because the string has a non‑trivial destructor; using std::string_view fixes this.
Automatic vectorization fails when loops contain branches or function calls; template‑based loops with constexpr conditions help.
Fighting the Compiler
Modern compilers are powerful; write compiler‑friendly code to let them optimize.
NRVO (Named Return Value Optimization)
struct Noisy { Noisy(){ std::cout << "constructed" << this << '
'; } Noisy(const Noisy&){ std::cout << "copy-constructed" << '
'; } Noisy(Noisy&&){ std::cout << "move-constructed" << '
'; } ~Noisy(){ std::cout << "destructed" << this << '
'; } };
Noisy f(){ Noisy v = Noisy(); return v; }
void g(Noisy arg){ std::cout << "&arg = " << &arg << '
'; }When NRVO applies, the object is constructed directly in the caller’s storage, avoiding copies and moves.
Misusing std::move
Noisy f(){ Noisy v = Noisy(); return std::move(v); }This disables NRVO and adds extra move constructions.
Factory returning std::optional
std::optional<Noisy> f(){ Noisy v = Noisy(); return v; }Returning std::optional prevents NRVO; returning the object directly is cheaper.
Automatic Vectorization
enum Type { kAdd, kMul };
int add(int a, int b){ return a + b; }
int mul(int a, int b){ return a * b; }
std::vector<int> func(std::vector<int> a, std::vector<int> b, Type t){
std::vector<int> c(a.size());
for(int i = 0; i < a.size(); ++i){
if(t == kAdd) c[i] = add(a[i], b[i]);
else c[i] = mul(a[i], b[i]);
}
return c;
}Using constexpr and inline functions makes the loop vectorization‑friendly.
Language Features (C++17)
Structured Bindings
for(const auto& [key, value] : map){ /* ... */ }Allows direct unpacking of map entries without intermediate variables.
Class Template Argument Deduction for std::pair and std::tuple
std::pair p{3.14, "pi"s}; // type deducedif constexpr
template<typename T> std::string convert(T input){
if constexpr(std::is_same_v<T, const char*> || std::is_same_v<T, std::string>)
return input;
else
return std::to_string(input);
}Init‑statement in if
if(auto it = m.find(10); it != m.end()) return it->second.size();std::shared_mutex
class ThreadSafeCounter{
public:
unsigned int get() const{ std::shared_lock lock(mutex_); return value_; }
unsigned int increment(){ std::unique_lock lock(mutex_); return ++value_; }
void reset(){ std::unique_lock lock(mutex_); value_ = 0; }
private:
mutable std::shared_mutex mutex_;
unsigned int value_ = 0;
};std::string_view
A lightweight, non‑owning view of a string that avoids copies when handling literals or substrings.
std::string_view remove_prefix(std::string_view str){ str.remove_prefix(3); return str; }try_emplace and insert_or_assign for associative containers
std::map<std::string, std::string> m;
m.try_emplace("c", 10, 'c'); // inserts only if key absent
m.insert_or_assign("c", "new value");std::any
Type‑safe container for any copyable type, with runtime type information and automatic destruction.
std::optional
Represents an optional value, safer than returning raw pointers or nullptr.
std::optional<ReturnType> func(const std::string& in){
if(in.empty()) return std::nullopt;
ReturnType ret; /* ... */
return ret;
}std::variant
Type‑safe union that can hold one of several types.
std::variant<ReturnType, Err> func(const std::string& in){
if(in.empty()) return Err{"input is empty"};
ReturnType ret; /* ... */
return ret;
}While std::variant lacks built‑in pattern matching, std::visit can be used for type‑specific handling.
Conclusion
C++17 adds many modern, safe, and expressive features—structured bindings, constexpr if, std::optional, std::variant, std::any, std::shared_mutex, and std::string_view —that help developers write clearer, more efficient code and avoid hidden performance costs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent Technical Engineering
Official account of Tencent Technology. A platform for publishing and analyzing Tencent's technological innovations and cutting-edge developments.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
