Xavier Lamorlette
Notes de lecture sur le livre “Effective Modern C++” de Scott Meyers, fusionnées avec d'autres notes sur C++11.

() and {} when creating objectsnullptr to 0 and NULLoverridenoexcept if they won't emit exceptionsconstexpr whenever possiblestd::unique_ptr for exclusive ownership resource managementstd::shared_ptr for shared ownership resource managementstd::weak_ptr for shared_ptr-like pointers that can danglestd::make_unique and std::make_shared to direct use of newstd::move and std::forwardstd::move on rvalue references, std::forward on universal referencesdecltype on auto && parameters to std::forward themstd::launch::async if asynchronicity is essentialstd::threads unjoinable on all pathsstd::atomic for concurrency, volatile for special memoryUniversal reference = forwarding reference:
function parameter declared like a rvalue reference (T && param) that memorises the type of reference (lvalue or rvalue) passed:
For template type deduction, the reference-ness of arguments is ignored.
decltype(expr) is the exact compile-time type of the expression.
int x = 3;
decltype(x) y = x;
const vector<int> vi;
using CIT = decltype(vi.begin());
CIT another_const_iterator;
If expr is a lvalue expression of type T and not a name, decltype(expr) is T &.
C++14 introduces decltype(auto), which is notably useful for templates writing since auto does not preserve all qualifiers.
const int a = 1;
auto x = a;
template <typename T> class TypeDeduction;
TypeDeduction<decltype(x)> xType;
This will generate a compilation error such as:
error: aggregate 'TypeDeduction<int> xType' has incomplete type
#include <boost/type_index.hpp>
std::cout << boost::typeindex::type_id_with_cvr<decltype(x)>().pretty_name();
auto generally decreases readability (but not in all cases, notably for STL iterators).
auto.
() and {} when creating objectsint a{0};
string s{"hello"};
string s2{s}; // copy construction
vector<string> vs{"alpha", "beta", "gamma"};
map<string, string> capitals {
{"fr", "Paris"},
{"uk", "London"}
};
double * pd = new double [3] {0.5, 1.2, 12.99};
class C {
int a = 7;
int x[4];
public:
C():
x{0, 1, 2, 3} {
}
};
struct A {
A(std::initializer_list<string> strs) {
for (const string & s: strs) {
[…]
}
};
}
My choice:
nullptr to 0 and NULLvoid f(int); // #1
void f(char *); // #2
f(0); // C++03: which f is called?
f(nullptr); // C++11: unambiguous, calls #2
using Doublet = tuple<int, double>;
Alias template:
template <typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
MyAllocList<Widget> widgetList;
C++11 type traits have equivalents in C++14 with _t suffix (using alias templates). Example:
std::remove_const<T>::type // C++11
std::remove_const_t<T> // C++14
enum Color {RED, GREEN, BLUE};
Scoped enum = strongly typed enum
enum class Color {RED, GREEN, BLUE};
Color color = Color::GREEN;
if (color == Color::RED)
Scoped enums can be forward declared:
enum class Color;
struct A {
A(const A &) = delete;
A & operator=(const A &) = delete;
};
overrideMethods specifiers:
final: method not overridabled (for a class: not usable as a base class)override: explicit override, checked by the compilerReference-qualified member functions:
DataType & data() & { // for lvalues instances
return values; // return a lvalue
}
DataType && data() && { // for rvalues instances
return std::move(values); // return a rvalue
}
For containers, cbegin() and cend() produce const_iterators (C++14).
insert() and erase() use const_iterators.
noexcept if they won't emit exceptionsint g() noexcept;
All memory de-allocation functions and destructors are implicitly noexcept.
noexcept allows compiler optimisations, notably for move, swap, memory de-allocations and destructors:
not generating code for exception handling means smaller code and cheaper calls to the function.
constexpr whenever possibleconstexpr means known during compilation (and, of course, const). Example:
constexpr auto arraySize = 10;
constexpr functions produce compile-time constants when called with compile-time constants.
Constructors and methods can be constexpr. Example:
constexpr Point(int xVal=0, int yVal=0) noexcept:
x(xVal),
y(yVal) {
}
constexpr int getX() const noexcept {
return x;
}
In C++14, even:
constexpr void setX(int xVal) noexcept {
x = xVal;
}
Declare default constructor, destructor, copy constructor, copy assignment, move constructor and move assignment operator = default
to make intentions clear, and to avoid move operations not being generated if defining one of those later.
Use standard value classes to rely on default move and copy operations.
For resource management, use smart pointers (along with = default where needed).
std::unique_ptr for exclusive ownership resource managementdelete (or an optional deleter function given at construction).
So the pointed object must have been allocated by new.shared_ptr can be constructed from unique_ptr.std::auto_ptr.std::shared_ptr for shared ownership resource managementstd::weak_ptr for shared_ptr-like pointers that can dangleshared_ptr: they don't affect the reference count.weak_ptr to null.weak_ptrs.shared_ptr by either:
lock() (result may be null);shared_ptr constructor (may throw an exception).std::make_unique and std::make_shared to direct use of newstd::make_shared<T>: constructs an object (via new) and wraps it in a shared_ptr.std::make_unique<T>: constructs an object (via new) and wraps it in a unique_ptr (C++14).std::move and std::forwardstd::move: unconditional cast to rvalue; transforms any sort of reference in a rvalue reference.
std::move(a) "~" static_cast<A &&>(a)
swap uses std::move.
Move requests on const objects (notably if using a rvalue reference to a const object) result in copies.
A parameter is always a lvalue, even if its type is a rvalue reference, hence the need for std::forward.
std::forward<T>: conditional cast to rvalue, if its argument was initialised with a rvalue.
Universal references (aka perfect forwarding references) arise when there is type deduction. They preserve the argument's value category (lvalue / rvalue) and its const / volatile modifiers.
template <typename T>
void f(T && param);
auto && x = y;
No const for universal references.
std::move on rvalue references, std::forward on universal referencesDo it:
Don't apply std::move on local objects because of RVO: Return Value Optimisation:
if a function returns a local variable, the copy is avoided by constructing it in the memory allocated for the function's return value.
Because universal references are much greedier than expected. Beware especially of perfect forwarding constructors.
In universal / forwarding references T &&, for lvalues, T resolves to lvalue reference, then collapsing rules apply.
Declaring a reference to reference is illegal.
SSO: Small String Optimisation: string < 15 characters are stored in a buffer within the std::string object.
Moving doesn't work for small strings and arrays (but can work for contained objects).
Some STL operations use only move operations declared noexcept, to offer strong exception safety guarantee.
Vocabulary:
this pointer will be by default captured by value.Init capture (C++14): generalised lambda capture. Example:
auto f = [data = std::move(data)] {
…
};
decltype on auto && parameters to std::forward themGeneric lambda (C++14): lambda that uses auto in its parameter specification. Example:
auto f = [](auto x) {
…
};
To forward:
auto f = [](auto && x) {
return g(std::forward<decltype(x)>(x));
};
For usage of auto && in generic programming see
“Auto Type Deduction in Range-Based For Loops” by Petr Zemek.
std::async deals automatically with software threads management (thread exhausting, oversubscription, load balancing) and exceptions.
int doAsyncWork();
auto futureResult = std::async(doAsyncWork);
…
futureResult.get()
std::launch::async if asynchronicity is essentialLaunch policies:
std::launch::async: run on a different threadstd::launch::deferred: may run only when get or wait is called on the returned futureauto fut = std::async(std::launch::async, f);
Beware thread local variables: with std::async you don't know if it is run in a different thread.
std::threads unjoinable on all pathsA thread is joinable when it is or could be running.
If a std::thread is destroyed while still joinable, the program execution is terminated.
Use RAII to solve this issue.
The result of a callee (typically a std::promise) is stored in a shared state before being get by the caller (typically via a std::future).
std::promise<void> p;
// detecting task
p.set_value();
// reacting task
p.get_future().wait();
std::atomic for concurrency, volatile for special memoryvolatile: special memory such as memory-mapped I/O. Tells the compiler to not perform optimisation on operations on this memory.
std::atomic is for data accessed from multiple threads without using mutexes.
void addName(std::string newName) {
names.push_back(std::move(newName));
}
// instead of:
void addName(const std::string & newName) {
names.push_back(newName);
}
void addName(std::string && newName) {
names.push_back(std::move(newName));
}
// or:
template <typename T>
void addName(T && newName) {
names.push_back(std::forward<T>(newName));
}
This costs only ane extra move.
Beware pass by value is subject to the slicing problem.
emplace_back (instead of push_back): construct directly in the container with the constructors parameters.
Also emplace_front (instead of push_front) and emplace (instead of insert).
La dernière mise à jour de cette page date de septembre 2020.
Le contenu de ce site est, en tant qu'œuvre originale de l'esprit, protégé par le droit d'auteur.
Pour tout commentaire, vous pouvez m'écrire à xavier.lamorlette@gmail.com.