Modern C++ Coding Style
This post is based upon the standard - not supported with VC compiler ! Try these techniques using GCC / LLVM compilers and QtCreator / CLion IDEs
Avoid C programming style idioms
-
= rather than
strcpy() for copying , == rather than strcmp()
for comparing
-
use const
, constexpr functions rather than #DEFINE, macros
-
Avoid void*,
unions, and raw casts (use static_cast instead)
-
use
std:string instead of char*
-
avoid pointer
arithmetic
-
To obey C
linkage conventions, a C++ function must be declared to have C
linkage
Avoid C# / Java programming style idioms
-
Minimize the
use of reference and pointer variables: use local and member
variables
-
Use abstract
classes as interfaces to class hierarchies; avoid “brittle base
classes,” that is, base classes with data members.
-
Use scoped
resource management (“Resource Acquisition Is Initialization”;
RAII)
-
Use
constructors to establish class invariants (and throw an exception
if it can’t)
-
Avoid “naked”
new and delete; instead, use containers (e.g., vector, string,
and map) and handle classes (e.g., lock and
unique_ptr).
- minimal
run-time reflection: dynamic_cast and typeid
- Namespaces are non-modular - headers can introduce name clashes - avoid using directives in header files, because it increases the chances of name clashes.
C++11 new features
Constructor Controls =delete and =default
type deduction - auto
constant expression compile time evaluation -
constexpr
In-class member initializers
Inheriting constructors
struct
derived : base
{
using
base::base; //
instead of deriving multiple constructor sigs
};
Lambda expressions
Move semantics
specify function may not throw exceptions -
noexcept
A proper name for the null pointer - nullptr
(instead of NULL)
The range-for statement
Override controls: final and override
Template Type aliases- binding arguments of
another template
template<class
T> struct Alloc {};
template<class
T> using Vec = vector<T,
Alloc<T>>;
//
type-id is vector<T, Alloc<T>>
Vec<int>
v; // Vec<int> is the same as vector<int,
Alloc<int>>
Typed and scoped enumerations: enum class
Universal and uniform initialization
Variadic templates
Hashed containers, such as unordered_map
basic concurrency library components: thread,
mutex, and lock
asynchronous computation: future, promise,
and async()
regexp
unique_ptr, shared_ptr
tuple
bind(), function
decltype(expr) // reuse a type
Basics
The stream idiom
// overloading IO stream ops:
ostream& operator<<(ostream& os, const Entry& e)
istream& operator>>(istream& is, Entry& e)
cout << , cin >> // stdio streams
getline() // helper function for making it easier to read from stdin
If a pointer to function is given as the second argument to <<, the function pointed to is called. cout<<pf means pf(cout). Such a function is called a manipulator
determine the memory footprint
of a variable, or the size of an array
sizeof(T)
extent
<decltype(
T )> ::value
//
size of an array must be a constant expression
Utilize initializer lists, auto, constexpr
use initializer lists {} instead of =, ()
- prevents narrowing conversions
with auto, need to use =
prefer constexpr to const -
compile time evaluation
iterating over a container:
v.begin()
and v.end() or begin(v) and end(v)
for
(const auto& x : v) //
for each x in v
for
(int& x : v) // to modify an element
in a range-for loop, the element variable should be a reference
Mechanisms: pass by
ref, const functions, operator overloading, subscripting
void doSomething(MyClass& c) {} // pass by
ref
const function:
double real() const { return re; }
operator overload: complex&
operator+=(complex z) { re+=z.re, im+=z.im; return *this; } // add to
re and im
double& operator[](int i) { return elem[i];
} // element access: subscripting
A common pattern: templatized RAII Handles as custom container wrappers
template<typename T>
class Handle { // Parameterized Type
private:
T* elem;
...
Handle(int s) :elem{new
double[s]}, sz{s} { ... } // constructor: acquire resources,
initializer_list<T>
~Handle() { delete[] elem;
}
// destructor: release resources
virtual double& operator[](int) = 0;
// pure virtual function
void doSomething(vector<Item*>&
v, int x) { // do something to all items
for (auto p :
v)
p–>doSomething(x);
}
Optimal techniques to pass arguments
and retvals
-
pass &
return references & from functions.
-
never return
a pointer *. can return a smart pointer.
-
Return
containers by value (relying on move for efficiency)
-
Each time a
function is called, a new copy of its arguments is created.
-
a pointer or
a reference to a local non-static variable should never be returned.
-
non-primative
arguments should be passed as const &
-
For template
arguments, an rvalue reference is used for “perfect
forwarding”
- Default
arguments may be provided for trailing arguments only
- the recommended way to pass containers in and out of functions: pass Containers by reference, return them as objects by value (implicitly uses move)
range-for over a handle
// To support the range-for loop for a container handle, must define suitable begin() and end() functions:
template<typename T>
T* begin(Handle<T>& x)
{
functors, lambdas, variadic
templates
// functor
bool operator()(const T& x) const {
return x<val; } // call operator
bind - // forward call wrapper
// lambda - eg capture all by ref
[&](const string& a){ return a<s;
}
// variadic template
template<typename T, typename... Tail>
void f(T head, Tail... tail)
ISO C++ standard char types
-
character
types: char (8), [signed | unsigned ] char, wchar_t
(unicode), char16_t (UTF), char32_t. L'AB'
-
<cctype>::
isspace(), isalpha(), isdigit(), isalnum()
ISO C++ standard int types
-
integer
types: [signed | unsigned] short | int | long | long long
-
::size_t
x = sizeof(xxx) ; // implementation defined type
-
<limits>::numeric_limits
// query arithmetic type properties
initialization gotchas – narrowing,
default values, initialization lists
-
bool b2
{7}; // error: narrowing
-
{}
indicates default value (nullptr for pointers)
-
if (p) {
// equivalent to p!=nullptr
-
auto -
use =, because {} is deduced to std::initializer_list<T>
-
The only
really good case for an uninitialized variable is a large input
buffer.
-
only stack
variables are default initialized !
function pointers
void (*pf)(string) = &DoSomething;
pf("blah");
using F = void(*)(string);
problems with passing arrays as
arguments
-
arrays cannot
be copied, assigned, or passed by val
-
extern "C"
int strlen(const char*); // from <string.h>
- the size of the array is lost to the called function.
-
an argument
of type T[] will be converted to a T* by val - preferable to pass a
reference to some container
Prefer references
int var = 0;
int& rr {var};
++rr;
// var is incremented by 1
int* pp = &rr; //
pp points to var
move uses “ref ref”
move(x) means static_cast<X&&>(x)
where X& is the type of x.
Simple constructs:
- struct, union, enum, enum class
- enum class vs enum
enum class Color { red, blue, green }; auto col = Color::red; // must be
- struct vs class - struct can be initialized using the {} notation
- order is important , no need for constructor. all members public.
function signature keywords
[[noreturn]] virtual inline auto f(const unsigned long int *const)
–> void const noexcept;
- avoid argument conversions for params: explicit // no implicit conversions
- 2 ways to specify an inline function:A) A member function defined within the class declaration is taken to be an inline member function. B) Use inline keyword
- specify a static function - keyword static in declaration - is not repeated in the definition
eg
template<class T, class U>
auto product(const vector<T>& x, const vector<U>& y) –> decltype(x*y);
What makes a function signature unique
-
unique
function signature check ignores const in params
-
Function
argument names are not part of the function type
-
an argument
is unused in a function definition by not naming it
Error handling
gracefully / ungracefully halting execution
if (something_wrong) exit(1);
terminate() // does not invoke
destructors. calls abort()
std::set_terminate()
quick_exit() , at_quick_exit()
basic guarantee vs strong guarantee
-
basic
guarantee for all ops: invariants of all objects are maintained, and
no resources are leaked
-
strong
guarantee for key ops: either the operation succeeds, or it has no
effect
-
nothrow
guarantee for some operations
handling
errors
Catch common exceptions in main
catch (...) {
cerr <<
the standard exception hierarchy
exception: logic_error, runtime_error, bad_exception, bad_alloc, bad_cast, bad_typeid,
logic_error: length_error, domain_error, invalid_argument, out_of_range, future_error
runtime_error: overflow_error, range_error, underflow_error, system_error, regex_error, ios_base::failure
useful members of the exception class
exception::what(), current_exception(), rethrow_exception(), make_exception_ptr(), exception_ptr, nested_exception, terminate()
catch exception& best practices for handling
exceptions
-
an exception
is potentially copied several times up the stack before it is caught
- don’t put huge amounts of data in exception body
-
innocent-looking operations (such as <, =, and sort())
might throw exceptions
-
error in a
thread can crash a whole program - add a catch-all handler to
main()
-
The body of a
function can be a try-block.
-
handle RAII
is neater than try catch finally
assertions
compiletime static_assert vs runtime macro <cassert>
assert
handling exceptions
in multithreaded code
packaged_task
does the following:
catch(...) { myPromise.set_exception(current_exception());
Source Files and Programs
the C++ compilation and linkage
process
-
A program is
a collection of separately compiled units combined by a linker
-
file is unit
of compilation
-
precompiler
--> translation unit
-
A program
must contain exactly one function called main()
-
An entity
must be defined exactly once in a program. It may be declared many
times, but the types must agree exactly.
-
Outside a
class body, an entity must be declared before it is used
static vs shared libraries
include semantics
#include <iostream> // from
standard include directory
#include "myheader.h" // from
current directory
external linkage
external
linkage - when definition is not in same TU as declaration.
extern
// used for external linkage
#ifdef __cplusplus
extern "C" {} //
a linkage block
…
using,
constexpr, static & const not accessible from other TUs -
hence extern const
Defining macros
#define PRINT(a,b) cout<<(a)<<(b)
## concatenate
Writing #x " = " rather than #x << " = " is obscure “clever code”
predefined macros: __FUNC__ , __FILE__ , __LINE__
best practices for reducing compile
time
-
Precompiled
headers - modern C++ implementations provide precompiling of header
files to minimize repeated compilation of the same header.
-
avoid global
variables
-
a header
should never contain: ordinary function definitions, fields, global
variables, unnamed namepsaces, using directives
-
To ensure
consistency (identical definition), place using aliases,
consts, constexprs, and inlines in header files
-
dont abuse
#include
-
minimize
dependencies
-
compiler
include guards
-
An unnamed
namespace {} can be used to make names local to a compilation unit -
internal linkage
Classes and OOP
A class provides 6 default methods
by
default a class provides 6 things
class X {
X();
// default constructor
X(const X&);
// copy constructor
X(X&&);
// move constructor
X& operator=(const X&);
// copy assignment: clean up target and copy
X& operator=(X&&);
// move assignment: clean up target and move
~X();
// destructor: clean up
When to customise the default functions of a class:
- If a class has a virtual function, it needs a virtual destructor
- If a class has a reference member, it probably needs copy operations
- If a class is a resource handle, it probably needs copy and move operations, constructor, a destructor, and non-default copy operations
- If a class has a pointer member, it probably needs a destructor and non-default copy operations
- Don't need a copy constructor just to copy an object - objects can by default be copied by assignment. default semantics is memberwise copy.
=default and =delete
constructor call order:
be aware that constructor is called on every copy and move
delegating constructor - when one constructor calls another
Constructors execute member and base constructors in declaration order (not the order of initializers)
A constructor can initialize members and bases of its class, but not members or bases of its members or bases
Prevent conversion of a pointer to a derived to a pointer to a base: make the base class a protected base
Defining a class
constructor initialization lists
const functions and mutable fields
friend class / functions
defining an abstract base class
virtual ~Base() = 0; // abstract base class definition (.cpp)
Nested Classes
A nested class has access to non-static members of its enclosing class, even to private members (just as a member function has), but has no notion of a current object of the enclosing class.
A class does not have any special access rights to the members of its nested class
different
types of initialization: default, copy, memberwise
MyClass o1 {};
// default initialization
MyClass o2 { "aaa", 77 }; // memberwise
initialization
MyClass o3 { o2 }; //
copy initialization
Operator Overloading
// stream out operator:
ostream& operator<<(ostream&, const string&);
calling
std::cout << s; is the same as operator<<(std::cout,s);
// overload the + and +=
operators for a Complex number type ?
complex operator+(complex a, complex b) {
return a +=
b; // access representation through +=. arguments passed by
value, so a+b does not modify its operands.
inline complex&
complex::operator+=(complex a) {
re += a.re;
im += a.im;
return *this;
// define a conversion operator:
MyClass::operator int() const { return i;
} // a conversion operator resembles a
constructor
// declaring an indexer
const MyClass& operator[] (const
int&) const;
// declaring a functor:
int operator()(int);
pair<int,int> operator()(int,int);
// the -> operator
smart pointers overload operator –>, new
increment /
decrement operators:
Ptr& operator++();
// prefix
Ptr operator++(int);
// postfix
Ptr& operator––();
// prefix
Ptr operator––(int);
// postfix
allocator / deallocator operators -
implicitly static
void* operator new(size_t);
// use for individual object
void* operator new[](size_t);
// use for array
void operator delete(void*, size_t);
// use for individual object
void operator delete[](void*, size_t);
// use for array
// user defined literal operator:
constexpr complex<double>
operator"" i(long double d) ;
the order of construction /
destruction in a class hierarchy
-
Objects are
constructed from the base up and destroyed top-down from derived
-
destructors
in a hierarchy need to be virtual
-
the
constructor of every virtual base is invoked (implicitly or
explicitly) from the constructor for the complete object
-
destructors
are simply invoked in reverse order of construction - a destructor
for a virtual base is invoked exactly once.
keywords virtual, override,
final:
-
override and
final are compiler hints
-
keywords
virtual, override, final should only appear in .h files
-
override -
The override specifier comes last in a declaration.
-
Virtual
functions - vtbl efficiency within 75%
-
final - can
make every virtual member function of a class final - add final
after the class name
class Derived final : public Base {
Scope rules for deriving from a base
class
-
deriving from
a base class can be declared private, protected, or public
-
If B is a
private base, its public and protected members can be used only by
member functions and friends of D. Only friends and members of D can
convert a D* to a B*
-
If B is a
protected base, its public and protected members can be used only by
member functions and friends of D and by member functions and
friends of classes derived from D. Only friends and members of D and
friends and members of classes derived from D can convert a D* to a
B*.
-
If B is a
public base, its public members can be used by any function. In
addition, its protected members can be used by members and friends
of D and members and friends of classes derived from D. Any function
can convert a D* to a B*.
Best practices for abstract classes and
virtual functions
-
An abstract
class should have a virtual destructor
-
A class with
a virtual function should have a virtual destructor;
-
An abstract
class typically doesn’t need a constructor
Best practices for declaring interfaces
and data members
-
Prefer public
members for interfaces;
-
Use protected
members only carefully when really needed; a protected
interface should contain only functions, types, and constants.
-
Don’t
declare data members protected; Data members are better kept
private & in the derived classes to match specific requirements.
Best practices deriving
from a base class
-
Don’t call
virtual functions during construction or destruction
-
Copy
constructors of classes in a hierarchy should be used with care (if
at all) to avoid slicing
-
A derived
class can override new_expr() and/or clone() to return an object of
its own type
-
covariant
return rule - Return Type Relaxation applies only to return types
that are pointers or references
-
a change in
the size of a base class requires a recompilation of all derived
classes
Almost Reflection
type traits to inspect code elements
<type_traits>
eg is_class<X> , is_integral<X>
eg static_assert(std::is_floating_point<T>::value ,"FP type expected");
Add_pointer<T>
enable_if and conditional,
declval<X>
Casting
different types of casts:
- const_cast for getting write access to something declared const
- static_cast for reversing a well-defined implicit conversion
- reinterpret_cast for changing the meaning of bit patterns
- at least dynamic_cast is run-time checked - it supports polymorphic class hierarchies
limits of dynamic casting
if (auto px = dynamic_cast<MyClass*>(py)) // dynamic_cast used an upcast - returns nullptrotherwise.
dont use a ref: dynamic_cast<MyClass&> - could throw a bad_cast
dynamic_cast cannot cast from a void*. For that, a static_cast is needed
crosscasting, dynamic dispatch. better to use visitor pattern
upcasting vs downcasting
class Manager : public Employee {}
void g(Manager mm, Employee ee) {
Employee* pe = &mm; // OK: every Manager is an Employee
Manager* pm = ⅇ // error: not every Employee is a Manager
pm–>level = 2; // error: ee doesn't have a level
pm = static_cast<Manager*>(pe); // brute force: works
void Manager::doSomething() const {
Employee:: doSomething(); // base method
doSomething(); // error
Custom Allocators
explicitly calling new
// placement new - edge case where we specify memory address
void C::push_back(const X& a){
new(p) X{a}; // copy construct an X with the value a in address p
void C::pop_back() {
p–>~X(); // destroy the X in address p
explicitly overloading allocators / deallocators
// explicit:
new(&s) string{"xxx"}; // placement new: explicitly construct string
s.~string(); // explicitly destroy string
for enum class, define operator|() , operator&()
allocator<T>
Templates
Advantages of Templates
-
faster &
easier to inline - no casting , or double dispatch
-
code for a
member function of a class template is only generated if that member
is used
-
Use templates
to express containers
-
excellent
opportunities for inlining, pass constant values as arguments -->
compile-time computation.
-
resembles
dynamic programming with zero runtime cost
Limitations of templates
-
no way to
specify generic constraints ... until concepts. so errors that
relate to the use of template parameters cannot be detected until
the template is used
-
A member
template cannot be virtual
-
copy
constructors, copy assignments, move constructors, and move
assignments must be defined as non-template operators or the default
versions will be generated.
-
A virtual
function member cannot be a template member function
-
Source Code
Organization: #include the .cpp definition of the templates in every
relevant translation unit
long compile times
-
caution is
recommended when mixing object-oriented and generic techniques - can
lead to massive code bloat for virtual functions
creating and using a
template
template<typename C>
class MyClass { };
MyClass<string> o;
// called a specialization
Members
of a template class are themselves templates parameterized by the
parameters of their template class.
template<typename C>
MyClass<C>::MyClass() {}
// ctor
template compile-time polymorphism
template<typename T>
void sort(vector<T>&);
// declaration
void f(vector<int>& vi,
vector<string>& vs) {
sort(vi); //
sort(vector<int>&);
sort(vs); //
sort(vector<string>&);
template<typename T, int max>
struct MyClass {};
template<typename T, int max>
void f1(MyClass<T,int>& o);
void f2(MyClass<string,128>&
o){
f1(o); //
compiler deduces type and non-type arguments from a call,
}
function
template overloads
sqrt(2); // sqrt<int>(int)
sqrt(2.0); // sqrt(double)
sqrt(z); //
sqrt<double>(complex<double>)
SFINAE
A
template substitution failure is not an error. It simply causes the
template to be ignored;
use
enable_if to
conditionally remove functions from overload
resolution
Template Aliases
using IntVector = vector<int>;
Best practices for
implementing Templates
-
If a class
template should be copyable, give it a non-template copy constructor
and a non-template copy assignment - likewise for move
-
Avoid nested
types in templates unless they genuinely rely on every template
parameter
Concepts Generic Constraints
-
Estd
namespace – template constraints
-
Ordered<X>,
Equality_comparable<T>, Destructible<T>,
Copy_assignable<T>, etc
-
implement a
concept as a constexpr templated function with static_assert
-
parameterized
--> constraint checks
Passing parameters to
templates
-
template
Parameters can be template types, built in types, values, or other
class templates!
-
literal types
cannot be used as template value parameters; function
templates cannot be used as template params
-
can specify
default types !
template<typename Target =string,
typename Source =string>
Target to(Source arg)
// convert Source to Target
{ ... }
auto x1 = to<string,double>(1.2);
// very explicit (and verbose)
auto x2 = to<string>(1.2);
// Source is deduced to double
auto x3 = to<>(1.2);
// Target is defaulted to string;
Source is deduced to double
auto x4 = to(1.2);
// the <> is redundant
partial specialization vs complete
specialization
-
partial
specialization
curbing code bloat - replicated code can cost megabytes of code
space, so cut compile and link times dramatically
-
complete
specialization - Specialize templates to optimize for important
cases
template<> argument deduction
// template argument can be deduced from the
function argument list, we need not specify it explicitly:
template<>
bool less(const char* a, const char* b)
techniques to reduce compilation
time when implementing templates :
X* p; // OK: no definition
of X needed
The curiously recurring
template pattern (CRTP)
a
class X derives
from a class template instantiation using X itself
as template argument
template<class
T>
class
Base {
//
methods within Base can use template to access members of Derived
};
class
Derived :
public
Base<Derived>
{
use
for static polymorphism (generic meta-programming )
Template
Metaprogramming
constexpr
is_polymorphic<T>
Conditional<(sizeof(int)>4),X,Y>{}(7);
// make an X or a Y and call it
Scoped<T>,On_heap<T>
template<typename T>
using Holder = typename
Obj_holder<T>::type;
is_pod<T>::value
iterator_traits
<type_traits>
Enable_if<Is_class<T>(),T>*
operator–>(); // select a member (for classes only)
std::is_integral and
std::is_floating_point,
variadic templates
template<typename T, typename... Args> //
variadic template argument list: one or more arguments
void printf(const char* s, T value, Args... args) //
function argument list: two or more arguments
template<typename F, typename... T>
void call(F&& f, T&&...
t){
f(forward<T>(t)...);
}
auto t = make_tuple("Hello tuple",
43, 3.15);
double d = get<2>(t);
Know the Standard-Library
<utility> operators & pairs
<tuple>
<type_traits>
<typeindex> use a type_info as a key or
hashcode
<functional> function objects
<memory> resource management pointers
<scoped_allocator>
<ratio> compile time rational arithmetic
<chrono> time utilities
<iterator>
begin, end
<algorithm>
<cstdlib> bsearch(), qsort()
<exception> exception class
<stdexcept> standard exceptions
<cassert> assert macro
<cerrno> C style error handling
<system_error> system error support
error_code, error_category,
system_error
<string>
strlen(), strcpy(),
<cctype> character classification
atof() , atoi()
<cwctype> wide character classification
<cstring> C-style string functions
<cwchar> C-style wide char functions
<cstdlib> C-style alloc functions
<cuchar> C-style multibyte chars
<regex>
<iosfwd> forward declerations of IO
fascilities
<iostream>
<ios> iostream bases
<streambuf> stream buffers
<istream> input stream template
<ostream> output stream template
<iomanip> Manipulator
<sstream> streams to/from strings
<cctype> char classification functions
<fstream> streams to/from files
<cstdio> printf family of IO
<cwchar> printf family of IO for wide
chars
<locale> cultures
<clocale> cultures C-style
<codecvt> code conversion facets
<limits> numerical limits
<climits> C-style integral limits
<cfloat> C-style float limits
<cstdint> standard integer type names
<new> dynamic memory management
<typeinfo> RTTI
<exception>
<initializer_list>
<cstddef> C-style language support
sizeof(), size_t, ptrdiff_t, NULL
<cstdarg> variable length function args
<csetjmp> C-style stack unwinding
setjmp , longjmp
<cstdlib> program termination , C-style
math functions abs() ,
div()
<ctime> system clock
<csignal> C-style signal handling
<complex>
<valarray>
<numeric>
<cmath>
<random>
<atomic>
<condition_variable>
<future>
<mutex>
<thread>
<cinttypes> type aliases for integrals
<cstdbool> C bool, true, false
<ccomplex>
<cfenv> C floating point stuff
<ctgmath>
STL Containers
STL features:
- concepts: sequence vs associative containers; adapters, iterators, aglorithms, allocators
- container adapters - eg stack, queue, deque - double ended queue
Iterators are a type of pointer used to separate algorithms and containers
push_back does a copy via back_inserter
forward_list - SLL
multiset / multimap - values can occur multiple times
unordered_X
Xstream_iterator
How to implement a range check vector – not sure if this is good idea …
//override operator [] with at()
prefer standard algorithms over handwritten loops
accumulate - aggregator algo
valarray // matrix slicing
unordered_map / set; multimap / set
bitset - array of bool
pair<T,U> tuple<W,X,Y,Z>
functions common to several
containers:
less<T>
copy(), find(), sort(), merge(), cmp(),
equiv(), swap() , binary_search(),splice()
size(), capacity()
at() - range checks
emplace // nice and terse
hash<T> - functor
const iterators, reverse iterators
reverse iterators: rbegin, rend
const iterators: cbegin, cend
Container and Algorithm requirements
- To
be an element type for a STL container, a type must provide copy or
move operations;
- arguments to
STL algorithms are iterators, not containers
- _if suffix is
often used to indicate that an algorithm takes a predicate as an
additional argument.
STL common algorithms:
lower_bound(), upper_bound(), iter_swap()
for_each(b,e,f)
sequence predicates: all_of(b,e,f) ,
any_of(b,e,f) none_of(b,e,f)
count(b,e,v) , count_if(b,e,v,f)
isspace()
find_if(b,e,f)
equal(b,e,b2)
pair(p1,p2) = mismatch(b,e,b2)
search(b,e,b2,e2) //
find a subsequence
transform(b,e,out,f)
copy(b,e,out), move(b,e,out)
copy_if(b,e,out,f)
unique, unique_copy
remove() and replace()
reverse()
rotate(), random_shuffle(), and partition() -
seperates into 2 parts
next_permutation(), prev_permutation() ,
is_permutation() - generate permutations of a sequence
fill(), generate_n(), uninitialized_copy,
uninitialized_fill assigning to and initializing elements of a
sequence. (unitialized_ is for low level objects that are not
initialized)
swap(), swap_ranges(), iter_swap()
sort(), stable_sort() - maintain order of equal
elements, partial_sort()
binary_search() - search sequence that is
pre-sorted
merge() - combine 2 pre-sorted sequences
set_union, set_intersection, set_difference
lexicographical_compare() - order words in
dictionaries
min & max
Use for_each() and transform() only when there
is no more-specific algorithm for a task
Iterating over a container
[b:e) end points to the one-beyond-the-last element of the sequence.
Never read from or write to *end.
the empty sequence has begin==end;
while (b!=e) { //
use != rather than <
// do
something
++b;
// go to next element
}
Iterator operations
input (++ , read istream), output
(++, write ostream), forward (++ rw), bidirectional(++, --), random
access
iterator_traits - select among algorithms based
on the type of an iterator
++p is likely to be more efficient than p++.
3 insert
iterators:
insert_iterator - inserts before
the element pointed to using insert().
front_insert_iterator - inserts
before the first element of a sequence using push_front().
back_insert_iterator - inserts
after the last element of the sequence using push_back().
efficiently moving items from
one container to another
copy(c1,make_move_iterator(back_inserter(c2))); //
move strings from c1 into c2
getting an iterator from a
reverse_iterator
Use base() to extract an iterator from a
reverse_iterator
raw array vs array type
bitset<N>
vs vector<bool>
pair vs tuple
-
pair<T,U>,
tuple<T...>
-
When using
pair, consider make_pair() for type deduction
-
When using
tuple, consider make_tuple() for type deduction
-
tie() can be
used to extract elements from a tuple as out params
basic_string<C>,
valarray<T>
New Techniques: Lambdas, smart pointers and move
Mitigating common memory management problems
mem management problems: leaks, premature deletion, double deletion
int* p2 = p1; // potential trouble
handles, RAII, move semantics eliminate problems
function adapters: bind() , ref(), mem_fn(), function<T>
<functional> - function adaptors - take a function as argument and return a functor that can be used to invoke it
bind(f,args) , mem_fn(f) , not(f) - currying, partial evaluation -.more easily expressed using lambdas. Uses _1 std::placeholders
ref() - like bind(), but doesnt dereference early - use to pass references as arguments to threads because thread constructors are variadic templates.useful for callbacks, for passing operations as arguments, etc.
A mem_fn() or a lambda can be used to convert the p–>f(a) calling convention into f(p,a)
function is specified with a specific return type and a specific argument type. Use function when you need a variable that can hold a variety of callable objects
int f(double);
function<int(double)> fct {f}; // initialize to f
fct = [](double d) { return round(d); }; // assign lambda to fct
Passing values in / out of Lambdas
lambda default is all by reference: [&]
[=] by value (copy). recommended for passing a lambda to another thread
mutable - capture values can change
for_each - just use ranged for
[&v...] variadic params can be captured
can capture the current object BY REFERENCE [this]
the minimal lambda expression is []{}
auto z4 = [=,y]()–>int { if (y) return 1; else return 2; } // OK: explicit return type
Passing lambdas around
std::function<R(AL)> where R is the lambda’s return type and AL is its argument list of types - function<void(char* b, char* e)>
can store a lambda in a variable of type auto - no two lambdas have the same type
3 types of smart pointers in C++11
unique_ptr<int> f(unique_ptr<int> p) {
++*p;
return p;
}
void f2(const unique_ptr<int>& p) {
++*p;
}
unique_ptr<int> p {new int{7}};
p=f(p); // error: no copy constructor
p=f(move(p)); // transfer ownership there and back
f2(p); // pass a reference
auto p = make_shared<MyStruct>(1,"Ankh Morpork",4.65);
unique_ptr<X> sp {new X}; // there is no make_unique ... yet
shared_ptr are copied ,unique_ptr are moved
- you obviously still need to know your pointer prefix operator overloads *, & :prefix unary * means “dereferenced contents of” and prefix unary & means “address of.”
Mixing containers and smart pointers
vector<unique_ptr<Item>> v;
// rather than vector<Item*> ,vector<Item>
// because all Items implicitly destroyed
move
template<class T> class Handle {
primitive types & their pointers are always copied, even when moved
move must leave the source object in a valid but unspecified state so its destructor can be called
a move cannot throw, whereas a copy might (because it may need to acquire a resource).
a move is often more efficient than a copy.
Because initializer_list elements are immutable, cannot apply a move constructor - use uninitialized_copy
copying vs moving
- the diff between lvalues and rvalues: lvalue - named & unmovable VS rvalue - temporary value that is movable
- copy constructors by default do memberwise copy - not good for containers !
A copy constructor and a copy assignment for a class X are typically declared to take an argument of type const X&
Item a2 {a1}; // copy initialization
Item a3 = a2; // copy assignment
Handle(const Handle& a); // copy constructor
Handle& operator=(const Handle a); // copy assignment
Handle(Handle&& a); // move constructor
Handle& operator=(Handle&& a); // move assignment
std::move(x) // explicit
=delete; supress default copy / move definitions
move vs forward
A move() is simply a cast to an rvalue: static_cast<Remove_reference<T>&&>(t);
forward() is for “perfect forwarding” of an argument from one function to another
<utility> swap()
<typeindex> comparing and hashing type_index (created from a type_info)
allocate / deallocate functions
p=a.allocate(n); // acquire space for n objects of type T
a.deallocate(p,n); // release space for n objects of type T pointed to by p
best practices for working
with smart pointers
-
unique_ptr
cannot be copied, but can be moved. When destroyed, its deleter is
called to destroy the owned object.
-
Shared
pointers in a multi-threaded environment can be expensive (because
of the need to prevent data races on the use count).
-
A destructor
for a shared object does not execute at a predictable time
-
Prefer
resource handles with specific semantics to smart pointers
-
Prefer
unique_ptr to shared_ptr.
-
Prefer
ordinary scoped objects to objects on the heap owned by a unique_ptr
-
Minimize the
use of weak_ptrs
-
a weak_ptr
can be converted to a shared_ptr using the member function lock().
if (auto q = p.lock()) {
}
else {
p.reset();
}
Basic Mathematics Support
query
numeric limits
specializations of the numeric_limits template
presented in <limits>
numeric_limits<unsigned char>::max()
<climits> DBL_MAX
and FLT_MAX C MACROS
valarray efficient matrix operations
valarray - slices and strides for matrices
slice_array, gslice_array, mask_array,
indirect_array
valarray<int> v {
{00,01,02,03},
// row 0
{10,11,12,13},
// row 1
{20,21,22,23}
// row 2
};
slice{0,4,1} describes the first row of v (row
0)
slice{0,3,4} describes the first column (column
0)
gslice is a “generalized slice” that
contains (almost) the information from n slices
accumulate() adds elements of a sequence using
their + operator:
inner_product(), partial_sum() and
adjacent_difference()
iota(b,e,n) assigns n+i to the ith element
of [b:e).
generate
random numbers
<random> random_device, Rand_int ,
Rand_double
auto gen =
bind(normal_distribution<double>{15,4.0},default_random_engine{});
for (int i=0; i<500; ++i) cout <<
gen();
Concurrency
different levels of concurrency and locking
atomic, thread, condition_variable, mutex
, future + promise, packaged_task, and async()
Creating a worker thread
thread t {F{x}}; //pass a functor constructor to a thread --> thread executes functor
function arguments - const refs for RO, pointers for out params
Advanced Threading Constructs in C++11
advanced locking: defer_lock & explicit lock()
condition_variable - ::wait(), ::notify_one()
promise<T>::set_value(), future<T>::get
packaged_task<Task_Type> ::get_future()
async::get
thread_local
creating a critical section
mutex m; // used to protect access to shared data
void f() {
unique_lock<mutex> lck {m}; // acquire the mutex m, automatically scoped
// ... manipulate shared data ...
}
atomics
simplest memory order is sequentially consistent
Atomics - Lock-free programming: load and
store
enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
};
r1 = y.load(memory_order_relaxed);
x.store(r1,memory_order_relaxed);
[[carries_dependency]] //for transmitting
memory order dependencies across function calls
kill_dependency() //for stopping the
propagation of such dependencies.
atomic<T> - compare_exchange_weak(),
compare_exchange_strong(), is_lock_free()
2 possible values of an atomic_flag are called
set and clear.
atomic_thread_fence, atomic_signal_fence
// barriers
volatile
simple multi-threaded programming
thread::join,
joinable, swap, detach
thread my_thread2 {my_task,ref(v)};
// OK: pass v by reference
this_thread::sleep_until(tp)
this_thread::sleep_for(d)
this_thread::sleep_for(milliseconds{10});
this_thread::yield()
thread_local // thread local
storage
::hardware_concurrency() // reports the number of tasks that
can simultaneously proceed with hardware support
::get_id() // get thread id
Threading gotchas
-
A thread is a
type-safe interface to a system thread. After construction, a thread
starts executing its task as soon as the run-time system can acquire
resources for it to run. Think of that as “immediately.”
-
Do not
destroy a running thread; Use join() to wait for a thread to
complete
-
don’t pass
pointers to your local data to other threads
-
beware of
by-reference context bindings in lambdas
-
a thread can
be moved but not copied
-
thread
constructors are variadic templates - to pass a reference to a
thread constructor, must use a reference wrapper
Locking
mutex - lock(), unlock(), try_lock()
recursive_mutex - can be repetedly aquired
without blocking
timed_mutex - try_lock_until(tp) ,
try_lock_for(d)
recursive_timed_mutex
lock_guard<T> - guard for a mutex -
simplest
unique_lock<T> - lock for a mutex -
supports timed ops
A mutex cannot be copied or moved
lock_guard<mutex> g {mtx};
// destructor does the necessary unlock() on its argument.
owns_lock() - check whether an acquisition
succeeded
Ensure that a
function is executed only once across multiple threads
once_flag
call_once()
Signalling between threads
condition_variable - ::wait(), ::wait_until()
::wait_for() , ::notify_one(), notify_all() - like a WaitHandle on a
unique_lock<mutex>
condition_variable_any - like a
condition_variable but can use any lockable object
The the promise-future
paradigm for 'async callbacks'
A future is a handle to a shared state. It is
where a task can retrieve a result deposited by a promise
The status of a future can be observed by
calling wait_for() and wait_until(). the result can be retrieved via
::get()
future<double> fd =
async(square,2);
double d = fd.get();
shared_future<T> - can read result
multiple times
auto handle = async(task,args);
res = handle.get()
// get the result
future_error exception with the error condition
broken_promise
promise ::set_value, ::set_exception
A packaged_task can be moved but not copied.
::get_future()
packaged_task<int(int)> pt1
{DoSomething};
pt1(1);
Don’t
set_value() or set_exception() to a promise twice. Don’t get()
twice from a future;
Use async() to launch simple
tasks
C++ is not C
mechanisms of dealing with IO
and strings
The C way:
<stdio> fopen(), fclose(), mode flag
printf(), fprintf(), sprintf()
stdin, stdout, stderr
scanf()
getc(), putc(), getchar(), putchar()
<string.h>: strlen(), strcpy(), strcat(),
strcmp(), strstr()
atoi
The C++ way: streams
<istream>, <ostream>
iostream
Forward declarations for stream types and stream
objects are provided in <iosfwd>
cin, cout, cerr, clog
<fstream> ifstream, ofstream,
fstream
<sstream> istringstream,
ostringstream, stringstream
ios::ate // write at end
use str() to read a
result
state functions: good(), eof(),
fail(), bad()
getline(), get(c)
put(), write() flush()
noskipws
cin >> c1 >> x >> c2 >>
y >> c3;
peek, seekg
ostream& operator<<(ostream&
os, const Named_val& nv) {
return os <<
'{' << nv.name << ':' << nv.value << '}';
basic_ios class manages the state of a stream:
buffers, formatting, locales, Error handling
streambuf
istreambuf_iterator and ostreambuf_iterator
An ostreambuf_iterator writes a stream of
characters to an ostream_buffer
sto*
(String to) functions
Time
The C way:
<ctime> clock_t, time_t, tm
The C++ way: duration
std::chrono namespace FROM <chrono>
high_resolution_clock, duration_cast<T>
duration, time_point
call now() for one of 3 clocks:
system_clock, steady_clock, high_resolution_clock
duration_cast<milliseconds>(d).count()
ratio<1>
duration<si45, micro>
// SI units from <ratio> constructed from
ratio<int,int>
Some low level C features may still be handy
in a C++ program
-
memory.h:
memcpy(), memove(), memcmp(), memset(), calloc(), malloc(),
realloc(), free()
- cmp() ,
qsort() , bsort()
- safe fast low level C style copy:
if (is_pod<T>::value) memcpy( ...
char* p = L“…”;
"" == const char* p = '\0';
prefxes: L for wide chars, R for raw strings
Some best practice Takeaways:
- using namespace std; // place in cpp files after includes to make your code more succinct
- prefer {}-style initializers and using for type aliases
Use constructor/destructor pairs to simplify resource management (RAII).
Use containers and algorithms rather than built-in arrays and ad hoc code
Prefer standard-library facilities to locally developed code
Use exceptions, rather than error codes, to report errors that cannot be handled locally
Use move semantics to avoid copying large objects
Avoid “naked” new and delete. Use unique_ptr / shared_ptr
Use templates to maintain static type safety (eliminate casts) and avoid unnecessary use of class hierarchies
Header files - semi-independent code fragments minimize compilation times
Pointers - a fixed-size handle referring to a variable amount of data “elsewhere”
use namespaces for scope: namespace {}
establish class invariants in constructors (throw exceptions fail fast)
static_assert - only works on constant expressions (useful in generics)