C++11 Features
Complete reference for C++11 language and library features — the revision that modernised C++ from the ground up.
C++11since C++11C++11 is the ISO standard ratified on 12 August 2011 that introduced move semantics, lambdas, variadic templates, a threading model, and dozens of other features that collectively transformed C++ into a modern systems language.
Overview
C++11 ended an eight-year gap after C++03 and is widely considered the dividing line between "old C++" and "modern C++". The changes span three tiers: core language semantics (move semantics, rvalue references), metaprogramming machinery (variadic templates, constexpr, decltype), and a dramatically expanded standard library (smart pointers, <thread>, <chrono>, <regex>, hash containers). Every feature described below requires at minimum a C++11-conforming compiler; later additions in C++14/17/20 are noted explicitly.
Core Language Features
Move Semantics and Rvalue References
C++11 introduced rvalue references (T&&) and the companion utilities std::move and std::forward. Move semantics allow classes to transfer ownership of resources instead of copying them.
#include <utility>
#include <vector>
class Buffer {
int* data_;
std::size_t size_;
public:
explicit Buffer(std::size_t n) : data_(new int[n]), size_(n) {}
// C++11: move constructor
Buffer(Buffer&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
// C++11: move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
~Buffer() { delete[] data_; }
};
Buffer make_buffer(std::size_t n) { return Buffer(n); } // NRVO or moveThe Rule of 5 (extended from Rule of 3 in C++11) means that if you define any of destructor, copy constructor, copy assignment, move constructor, or move assignment, you must consider all five.
Lambda Expressions
#include <algorithm>
#include <vector>
void demo_lambdas() {
std::vector<int> v = {4, 1, 7, 2, 9, 3};
// C++11: basic capture by value and reference
int threshold = 5;
auto count_above = std::count_if(v.begin(), v.end(),
[threshold](int x) { return x > threshold; }); // captures threshold by value
// C++11: mutable lambda — local copy is modifiable
int total = 0;
std::for_each(v.begin(), v.end(),
[&total](int x) mutable { total += x; });
// C++11: immediately invoked
auto result = [](int a, int b) { return a * b; }(6, 7); // 42
}Lambdas generate a unique, unnamed closure type. The type is not speakable; use auto or std::function<R(Args...)> (C++11) when you need to store one.
auto and decltype
#include <map>
#include <string>
// C++11: auto deduces from initialiser, strips cv-qualifiers and references
auto x = 42; // int
auto& y = x; // int&
const auto z = x; // const int
// C++11: decltype preserves exact type including references
decltype(x) a = 0; // int
decltype((x)) b = x; // int& — extra parens = lvalue expression
// C++11: trailing return type for dependent types
template<typename A, typename B>
auto multiply(A a, B b) -> decltype(a * b) {
return a * b;
}
// C++14 simplification: return type deduction without trailing syntax
// template<typename A, typename B>
// auto multiply(A a, B b) { return a * b; }
void iterate(const std::map<std::string, int>& m) {
for (auto it = m.begin(); it != m.end(); ++it) { // C++11
// it is std::map<std::string,int>::const_iterator
}
}Range-Based for
#include <vector>
#include <string>
void process(std::vector<std::string>& names) {
for (auto& name : names) { // C++11: by reference
name += " (processed)";
}
for (const auto& name : names) { // C++11: by const reference — prefer for read-only
// use name
}
}The loop calls begin() and end() via ADL, so it works on any type that provides those — including your own containers.
Uniform Initialisation
C++11 standardised brace-init ({}) as a universal syntax that prevents narrowing conversions and works on aggregates, constructors, and even scalars.
#include <vector>
#include <string>
struct Point { int x, y; };
void init_examples() {
int a{42}; // scalar
Point p{1, 2}; // aggregate
std::vector<int> v{1, 2, 3, 4}; // initializer_list constructor
std::string s{"hello"};
// Narrowing is ill-formed — caught at compile time
// double d = 3.14;
// int bad{d}; // error: narrowing conversion
}std::initializer_list<T> (C++11) is the mechanism that lets constructors accept brace-enclosed lists.
nullptr
void f(int);
void f(char*);
// C++98: f(NULL) — ambiguous or calls f(int)
// C++11: f(nullptr) — unambiguously calls f(char*)
f(nullptr);
int* p = nullptr; // clearly a null pointer, not integer 0constexpr
constexpr int factorial(int n) { // C++11: must be a single return statement
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int f120 = factorial(5); // evaluated at compile time
// C++14 relaxed constexpr: loops and local variables allowed
// C++20 constexpr: virtual functions, try/catch, and morenoexcept
void swap_safe(int& a, int& b) noexcept { // C++11
int tmp = a; a = b; b = tmp;
}
// noexcept(expr) — conditional specification
template<typename T>
void move_if_noexcept_demo(T& a, T& b)
noexcept(noexcept(std::swap(a, b))) {
std::swap(a, b);
}Marking move constructors and swap noexcept is critical: std::vector will use moves during reallocation only if they are guaranteed not to throw.
Variadic Templates
#include <iostream>
// C++11: parameter pack
template<typename T>
void print(T val) { std::cout << val << '\n'; }
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << ' ';
print(rest...); // recursive pack expansion
}
// C++17 fold expressions make this cleaner, but C++11 recursion works
print(1, "hello", 3.14, 'x');Scoped Enumerations
// C++98: unscoped, pollutes enclosing namespace, implicit int conversion
enum Color98 { Red, Green, Blue };
// C++11: scoped, no implicit conversion, controllable underlying type
enum class Color : uint8_t { Red, Green, Blue };
Color c = Color::Red; // must qualify
// int i = c; // error: no implicit conversion
int i = static_cast<int>(c); // explicit onlyoverride and final
struct Base {
virtual void draw() const;
virtual void resize(int factor);
};
struct Derived : Base {
void draw() const override; // C++11: compiler verifies override
void resize(int factor) override;
// void drwa() const override; // C++11: error caught at compile time
};
struct Leaf final : Derived { // C++11: cannot be further derived
void draw() const override;
};Deleted and Defaulted Functions
struct NonCopyable {
NonCopyable() = default; // C++11: explicit default
NonCopyable(const NonCopyable&) = delete; // C++11: suppress copy
NonCopyable& operator=(const NonCopyable&) = delete;
};
// Also useful to delete non-sensical overloads:
void process(int);
void process(double) = delete; // prohibit accidental float->double->int chainstatic_assert
template<typename T>
void must_be_integral(T) {
static_assert(std::is_integral<T>::value, // C++11
"T must be an integral type");
}
// C++17: static_assert(expr) without message is also validLibrary Additions
Smart Pointers
#include <memory>
// C++11: unique ownership
auto buf = std::make_unique<int[]>(1024); // make_unique: C++14
std::unique_ptr<int[]> raw(new int[1024]); // C++11 form
// C++11: shared ownership with reference counting
auto shared = std::make_shared<std::string>("hello");
std::weak_ptr<std::string> weak = shared; // non-owning observer
if (auto locked = weak.lock()) { // safe access
// locked is shared_ptr, shared stays alive
}Prefer std::make_shared (C++11) over new; it performs a single allocation for both the object and control block.
Threading
#include <thread>
#include <mutex>
#include <atomic>
std::mutex mtx;
std::atomic<int> counter{0}; // C++11: lock-free for suitable types
void worker(int id) {
++counter; // atomic increment
std::lock_guard<std::mutex> lk(mtx); // C++11: RAII lock
// critical section
}
void launch() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
}C++11 defined the first standardised memory model, making portable concurrent code possible without platform-specific intrinsics.
std::function and std::bind
#include <functional>
int add(int a, int b) { return a + b; }
std::function<int(int, int)> fn = add; // C++11: type-erased callable
auto add5 = std::bind(add, std::placeholders::_1, 5); // C++11: partial application
int result = add5(3); // 8Prefer lambdas over std::bind in C++14+ for readability and performance; std::bind is retained for completeness.
Hash Containers
#include <unordered_map>
#include <unordered_set>
std::unordered_map<std::string, int> freq; // C++11: average O(1) lookup
freq["foo"]++;
freq.reserve(1024); // hint bucket count upfront<chrono>
#include <chrono>
auto start = std::chrono::steady_clock::now(); // C++11
// ... work ...
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);Best Practices
- Default to
autofor iterator and complex return types; reserve explicit types where clarity is needed for readers. - Mark move constructors
noexcept— containers use this information to avoid defensive copies during reallocation. - Prefer
unique_ptrover rawnew/delete; escalate toshared_ptronly when shared ownership is genuinely required. - Use
overrideon every overriding function — it turns silent signature mismatches into compile errors. - Replace
NULLand0for pointers withnullptreverywhere; it is typed and resolves overloads correctly. - Avoid
std::bindin new code; a lambda captures intent and compiles to better code.
Common Pitfalls
initializer_listconstructor hijacking:std::vector<int> v(10, 0)creates 10 zeros;std::vector<int> v{10, 0}creates two elements. Always know whether a type has aninitializer_listconstructor.autostrips references andconst:auto x = some_ref;gives a copy, not a reference. Useauto&orconst auto&deliberately.- Moved-from objects are valid but unspecified: after
std::move(obj), do not readobjuntil you have assigned it a known value. - Lambda capture by reference outliving the captured variable: capturing a local by
&in a lambda stored beyond the enclosing scope causes undefined behaviour. std::threaddestructor callsstd::terminateif the thread is joinable and was not joined or detached — always join or detach before destruction.