Xavier Lamorlette
Notes de lecture sur le livre “Effective Modern C++” de Scott Meyers, fusionnées avec d'autres notes sur C++11.
Sommaire :
()
and {}
when creating objectsnullptr
to 0
and NULL
override
noexcept
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 new
std::move
and std::forward
std::move
on rvalue references, std::forward
on universal referencesdecltype
on auto &&
parameters to std::forward
themstd::launch::async
if asynchronicity is essentialstd::thread
s 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 NULL
void 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;
};
override
Methods 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 new
std::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::forward
std::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::thread
s 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.