Bjarne Stroustrup famous design axiom for C++ still holds true: "You don’t pay for what you don’t use. And what you do use, you couldn’t hand-code any better."
However, modern C++ features vary wildly. Some are completely eliminated by the compiler (Zero-Cost), some introduce a negligible wrapper cost, and others carry heavy runtime penalties if used carelessly.
Here is a first incomplete classification from a hardware and assembly perspective, compared directly to pure C:
1.Zero-Cost Abstractions (0% Runtime Overhead vs. C)
These language features exist purely at compile time. In the final machine code, they are indistinguishable from hand-written C code.
References (& vs. Pointers):
Under the hood: Resolved entirely by the optimizer into raw memory addresses (mov/lea). A reference consumes zero memory and zero extra clock cycles at runtime.C++20 Concepts & Templates:
Under the hood: Compile-time polymorphism. They produce the exact same specialized machine functions as manually duplicated C functions.constexpr/consteval:
Under the hood: Computations are fully shifted to compile time. The resulting value lands as a hard constant directly in the binary (mov eax, 42).Enum Classes (
enum class):
Under the hood: Strong typing that degrades to a simple integer (intoruint8_t) at runtime. Zero overhead.Namespaces:
Under the hood: Purely a linker/compiler naming convention. Non-existent at runtime.RAII (Destructors at Scope Exit):
Under the hood: The compiler injects the cleanup code (e.g., stack unwinding/deallocation) precisely where the scope ends. This is often more efficient and safer than manualfree()in C because it avoids error-pronegoto cleanup;spaghetti code.
2.Low-Cost Abstractions (Minimal, Mostly Negligible Overhead)
These features introduce a tiny layer of abstraction, but they are designed so hardware-efficiently that the impact is practically negligible (often < 1 clock cycle or purely static).
std::unique_ptr:
Under the hood: A naked pointer disguised as an object. No reference counting, no heap overhead beyond the raw pointer itself. The delete call at scope exit resolves to a standard deallocation function (free), which would be required anyway.Move Semantics (std::move):
Under the hood: A pure static_cast to an rvalue reference. The actual "move" is a fast register or pointer copy (e.g., 3 pointers for a std::vector), avoiding deep heap copies.Stateless Lambdas:
Under the hood: Translated by the compiler into an ordinary function or an inline-expandable function object. If they capture no state ([]), they cost exactly zero runtime overhead.std::vector (Bounds-Free
operator[]):
Under the hood: Equivalent to a raw C array with pointer arithmetic, provided you avoid.at()(which adds bounds checking).
3.High-Cost Abstractions (Significant Runtime Overhead)
These features solve complex software design problems at the cost of execution time, memory, or deterministic behavior. When used carelessly, they severely degrade performance.
std::shared_ptr&std::weak_ptr(Thread-Safe Reference Counting):
Under the hood: Every copy operation triggers an atomic increment/decrement (lock xaddon x86), which forces expensive cache-coherency bus locks on multi-core CPUs. Additionally, creation requires a separate heap allocation for the control block.Virtual Methods & Dynamic Polymorphism (
virtual, vtable):
Under the hood: Every call requires a pointer indirection via the vtable (call qword ptr [rax+24]). This destroys the CPU's branch prediction, prevents compiler inlining, and causes pipeline stalls.Exceptions (
try/catch/throw):
Under the hood: In the "happy path" (no error), modern zero-cost exception handling has minimal overhead. However: When an exception is thrown, the system must parse RTTI and unwind tables to walk the stack backward, which stalls execution by orders of magnitude. Many embedded projects ban exceptions entirely to guarantee determinism.dynamic_cast:
Under the hood: Uses RTTI (Run-Time Type Information) to inspect inheritance hierarchies at runtime and verify types. This consumes significant CPU cycles and should be strictly avoided in performance-critical loops.Uncontrolled Container Allocations (
std::map,std::list,std::unordered_map):
Under the hood: Every element inserted into astd::map(Red-Black tree) orstd::listforces a separate heap allocation (malloc/new), leading to severe cache fragmentation and cache misses.
What are your experiences with C++ and run time costs? Where have you encountered unexpected performance bottlenecks in your projects?
Top comments (0)