A Tour of C++
Bjarne Stroustrup
Chapter 1. The Basics
The semantics of argument passing are identical to the semantics of copy initialization (type checked and implicit argument type conversion)
initialization: universal form based on curly-brace-delimited initializer lists:
complex<double> z {d1, d2};
complex<double> z3 = {1, 2}; // the = is optional and redundant
the problems caused by implicit narrowing conversions is a price paid for C compatibility (if in doubt, use the general {}-list form)
a variable should only be left uninitialized in extremely rare circumstances. don't introduce a name until you have a suitable value for it.
With auto, we use the = because there is no potentially troublesome type conversion involved (type is deduced from the initializer)
use auto except when we want to make the type clearly visible, or want to be explicit about variables' range or precision (e.g. double rather than float)
scope: local, class, namespace
constexpr: meaning roughly "to be evaluated at compile time". For a function to be usable in constexpr, define this like this: constexpr double square(double x) { return x * x; }
use overloading when functions perform conceptually the same task on different types
if a function may have to be evaluated at compile time, declare it constexpr
declare one name only per declaration
prefer {}-initializer syntax for declaration with a named type
perfer = syntax for initialization in declarations using auto
avoid unintialized variables
don't declare a variable until you have a value to initialize it with
don't say in comments what can be clearly stated in code
Chapter 2. User-Defined Types
a union is a struct in which all members are allocated at the same address so that the union occupies only as much space as its largest member
to avoid errors, one can encapsulate a union so that the correspondence between a type field and access to the union members is guaranteed. At the application level, abstractions relying on such tagged unions are common and useful, but use of "naked" unions is best minimized.
Avoid "naked" unions; wrap them in a class together with a type field
enum class is strongly typed and its enumerators are scoped
enum class Color { read, blue, green };
enum class Traffic_light { green, yellow, red };
Color col = Traffic_light::red; // error: that red is not a Color
int i = Color::red; // error
Traffic_light& operator++(Traffic_light &t) {
switch(t) {
case Traffic_light::green: return t = Traffic_light::yellow;
case ...
case ...
}
}
Traffic_light next = ++light;
Prefer class enums over "plain" enums to minimize surprises.
Chapter 3. Modularity
C++ represents interfaces by declarations
c++ supports separate compilation
Avoid non-inline function definitions in headers
Don't put a using-directive in a header file
namespace: some declarations belong together and their names should not clash with others
The exception handling mechanism will exit scopes and function as needed (unwind) to get back to a caller that has expressed interest in handling that kind of exception, invoking destructors along the way as needed.
#include <stdexcept>
double& Vector::operator[](int i) {
if (i < 0 || size() <= i)
throw out_of_range("Vector::operator[]");
return elem[i];
}
void f(Vector &v) {
// ...
try {
v[v.size()] = 7;
} catch (out_of_range) {
// handle range error
}
// ...
}
a function that should never throw an exception can be declared noexcept: void user(int sz) noexcept { ... }. If user() still throws, terminate() is called.
class invariant: statement assumed to be true for a class --> it is the job of a constructor to establish the invariant for its class
handling an exception simply means doing some minimal local cleanup and rethrowing the exception
Vector::Vector(int s) {
if (s < 0)
throw length_error{};
elem = new double[s];
sz = s;
}
void test() {
try {
Vector v(-27);
}
catch (std::length_error) {
cout << "test failed: length error\n";
throw; // rethrow
}
catch (std::bad_alloc) {
std::terminate();
}
}
You can define your own classes to be used as exceptions and have them carry arbitrary information from a point where an error is detected to a point where it can be handled.
static_assert(A, S) prints S as a compiler error message if A is not true. Most important uses of static_assert come when we make assertions about types used as parameters in generic programming. For runtime-checked assertions, use exceptions.
What can be checked at compile time is usually best checked at compile time (using static_assert)
Throw an exception to indicate that you cannot perform an assigned task
Use exceptions for error handling
Develop an error-handling strategy early in a design
Use purpose-designed user-defined types as exceptions (not built-in types)
Don’t try to catch every exception in every function
If your function may not throw, declare it noexcept
Let a constructor establish an invariant, and throw if it cannot
Design your error-handling strategy around invariants
Chapter 12. Numerics
<cmath> errors are reported by setting errno from <cerrno> to EDOM for a domain error and to ERANGE for a range error.
void f() {
errno = 0; // clear old error state
sqrt(−1);
if (errno==EDOM) cerr << "sqrt() not defined for negative argument";
errno = 0; // clear old error state
pow(numeric_limits<double>::max(),2);
if (errno == ERANGE) cerr << "result of pow() too large to represent as a double";
}
Consider accumulate() , inner_product() , par tial_sum() , and adjacent_difference() before you write a loop to compute a value from a sequence
iota(b,e ,v): For each element in [ b : e ) assign ++v
Bind an engine to a distribution to get a random number generator
Be careful that your random numbers are sufficiently random
using my_engine = default_random_engine; // type of engine
using my_distribution = uniform_int_distribution<>; // type of distribution
my_engine re {}; // the default engine
my_distribution one_to_six {1,6}; // distribution that maps to the ints 1..6
auto die = bind(one_to_six,re); // make a generator
int x = die(); // roll the die: x becomes a value in [1:6]
class Rand_int {
public:
Rand_int(int low, int high) :dist{low,high} { }
int operator()() { return dist(re); } // draw an int
private:
default_random_engine re;
uniform_int_distribution<> dist;
};
the standard library provides (in <valarray> ) a vector -like template, called valarray , that is less general and more amenable to optimization for numerical computation
void f(valarray<double>& a1, valarray<double>& a2)
{
valarray<double> a = a1∗3.14+a2/a1; // numer ic array operators *, +, /, and =
a2 += a1∗3.14;
a = abs(a);
double d = a2[7];
// ...
}
In <limits> , the standard library provides classes that describe the properties of built-in types
static_assert(numeric_limits<char>::is_signed,"unsigned characters!");
static_assert(100000<numeric_limits<int>::max(),"small ints!");
Chapter 13. Concurrency
We call a computation that can potentially be executed concurrently with other computations a task. A thread is the system-level representation of a task in a program.
void f(); // function
struct F { // function object
void operator()(); // F’s call operator
};
void user() {
thread t1 {f}; // f() executes in separate thread
thread t2 {F()}; // F()() executes in separate thread
t1.join(); // wait for t1
t2.join(); // wait for t2, wait for the thread to terminate
}
passing arguments: uses a thread variadic template constructor that can accept an arbitrary sequence of arguments. The ref() is a type function from <functional> that unfortunately is needed to tell the variadic template to treat some_vec as a reference
void f(vector<double>& v);
struct F {
vector<double>& v;
F(vector<double>& vv) :v{vv} { }
void operator()();
};
int main() {
vector<double> some_vec {1,2,3,4,5,6,7,8,9};
vector<double> vec2 {10,11,12,13,14};
thread t1 {f,ref(some_vec)};
thread t2 {F{vec2}};
t1.join();
t2.join();
}
returning results: (but not very elegant)
void f(const vector<double>& v, double∗ res);
class F {
public:
F(const vector<double>& vv, double∗ p) :v{vv}, res{p} { }
void operator()();
private:
const vector<double>& v;
double∗ res;
};
int main() {
vector<double> some_vec;
vector<double> vec2;
// ...
double res1;
double res2;
thread t1 {f,cref(some_vec),&res1};
thread t2 {F{vec2,&res2}};
t1.join();
t2.join();
cout << res1 << ' ' << res2 << '\n';
}
Sharing data: The unique_lock ’s constructor acquires the mutex (through a call m.lock())
mutex m; // controlling mutex
int sh; // shared data
void f() {
unique_lock<mutex> lck {m}; // acquire mutex
sh += 7; // manipulate shared data
} // release mutex implicitly
this is error-prone, and equally obviously we try to make the correspondence clear through various language means. For example: class Record { public: mutex m; //... };
The standard library offers help in the form of an operation for acquiring several locks simultaneously:
void f() {
// ...
unique_lock<mutex> lck1 {m1,defer_lock};
unique_lock<mutex> lck2 {m2,defer_lock};
unique_lock<mutex> lck3 {m3,defer_lock};
// ...
lock(lck1,lck2,lck3); // ... manipulate shared data ...
} // implicitly release all mutexes
The destructors for the individual unique_lock s ensure that the mutex es are released when a thread leaves the scope.
<chrono>: this_thread refers to the one and only thread
using namespace std::chrono;
auto t0 = high_resolution_clock::now();
this_thread::sleep_for(milliseconds{20});
auto t1 = high_resolution_clock::now();
cout << duration_cast<nanoseconds>(t1−t0).count() << " nanoseconds passed\n";
<condition_variable>: it allows a thread to wait for some condition (often called an event) to occur as the result of work done by other thread s.
class Message { // object to be communicated
// ...
};
queue<Message> mqueue; // the queue of messages
condition_variable mcond; // the variable communicating events
mutex mmutex; // the locking mechanism
void consumer() {
while(true) {
unique_lock<mutex> lck{mmutex}; // acquire mmutex
while (mcond.wait(lck)) /* do nothing */; // release lck and wait;
// re-acquire lck upon wakeup
auto m = mqueue.front(); // get the message
mqueue .pop();
lck.unlock(); // release lck
// ... process m ...
}
}
void producer() {
while(true) {
Message m;
// ... fill the message ...
unique_lock<mutex> lck {mmutex}; // protect operations
mqueue .push(m);
mcond.notify_one(); // notify
} // release lock (at end of scope)
}
Communicating tasks: allow programmers to operate at the conceptual level of tasks (work to potentially be done concurrently) rather than directly at the lower level of threads and locks:
[1] future and promise for returning a value from a task spawned on a separate thread
[2] packaged_task to help launch tasks and connect up the mechanisms for returning a result
[3] async() for launching of a task in a manner very similar to calling a function.
These facilities are found in <future> .
future and promise: When a task wants to pass a value to another, it puts the value into a promise . Somehow, the implementation makes that value appear in the corresponding future , from which it can be read (typically by the launcher of the task).
f we have a future<X> called fx , we can get() a value of type X from it:
X v = fx.g et(); // if necessary, wait for the value to get computed
If the value isn’t there yet, our thread is blocked until it arrives. If the value couldn’t be computed, get() might throw an exception
void f(promise<X>& px) { // a task: place the result in px
// ...
try {
X res;
// ... compute a value for res ...
px.set_value(res);
}
catch (...) { // oops: couldn’t compute res
px.set_exception(current_exception());
}
}
void g(future<X>& fx) { // a task: get the result from fx
// ...
try {
X v = fx.get(); // if necessary, wait for the value to get computed
// ... use v ...
}
catch (...) { // oops: someone couldn’t compute v
// ... handle error ...
}
}
The packaged_task type is provided to simplify setting up tasks connected with future s and promise s to be run on thread s. A packaged_task provides wrapper code to put the return value or exception from the task into a promise
double accum(double∗ beg, double∗ end, double init) { // compute the sum of [beg:end) starting with the initial value init
return accumulate(beg,end,init);
}
double comp2(vector<double>& v) {
using Task_type = double(double∗,double∗,double); // type of task
packaged_task<Task_type> pt0 {accum};
packaged_task<Task_type> pt1 {accum}; // package the task (i.e., accum)
future<double> f0 {pt0.get_future()};
future<double> f1 {pt1.get_future()}; // get hold of pt1’s future
double∗ first = &v[0];
thread t1 {move(pt0),first,first+v.size()/2,0};
thread t2 {move(pt1),first+v.size()/2,first+v.size(),0}; // start a thread for pt1// start a thread for pt1
// ...
return f0.get()+f1.get(); // get the results
}
Treat a task as a function that may happen to run concurrently with other tasks.
To launch tasks to potentially run asynchronously, we can use async() :
double comp4(vector<double>& v) { // spawn many tasks if v is large enough
if (v.siz e()<10000) // is it worth using concurrency?
return accum(v.begin(),v.end(),0.0);
auto v0 = &v[0];
auto sz = v.size();
auto f0 = async(accum,v0,v0+sz/4,0.0);
auto f1 = async(accum,v0+sz/4,v0+sz/2,0.0);
auto f2 = async(accum,v0+sz/2,v0+sz∗3/4,0.0);
auto f3 = async(accum,v0+sz∗3/4,v0+sz,0.0);
return f0.get()+f1.g et()+f2.g et()+f3.g et(); // collect and combine the results
}
Using async() , you don’t hav e to think about threads and locks. Instead, you think just in terms of tasks that potentially compute their results asynchronously. There is an obvious limitation: Don’t even think of using async() for tasks that share resources needing locking
Work at the highest level of abstraction that you can afford
Atomics allow for lock-free programming
Leave lock-free programming to experts
A thread is a type-safe interface to a system thread
Use join() to wait for a thread to complete
Avoid explicitly shared data whenever you can
Use unique_lock to manage mutexes
Use lock() to acquire multiple locks
Use condition_variables to manage communication among threads
Think in terms of tasks that can be executed concurrently, rather than directly in terms of
threads
Prefer packaged_task and futures over direct use of threads and mutexes
Return a result using a promise and get a result from a future
Use packaged_tasks to handle exceptions thrown by tasks and to arrange for value return;
Use a packaged_task and a future to express a request to an external service and wait for its response
Use async() to launch simple tasks
Chapter 14. History and Compatibility
C-style casts should have been deprecated in favor of named casts: static_cast, reinterpret_cast, const_cast
Widg et∗ pw = static_cast<Widget∗>(pv); // pv is a void* supposed to point to a Widget
auto dd = reintrepret_cast<Device_driver∗>(0xFF00); // 0xFF is supposed to point to a device driver
char∗ pc = const_cast<char∗>("Casts are inherently dang erous");
Explicit type conversion can be completely avoided in most high-level code, so consider every cast (however expressed) a blemish on your design.
For class hierachy navigation, prefer the checked dynamic_cast
Macro substitution is almost never necessary in C++. Use const, constexpr, enum or enum class to define manifest constants, inline to avoid function-calling overhead, templates to specify families of functions and types, and namespaces to avoid name clashes.
Don’t use malloc() . The new operator does the same job better, and instead of realloc() , try a vector. Don’t just replace malloc() and free() with ‘‘naked’’ new and delete
Avoid void∗ , unions, and casts, except deep within the implementation of some function or class.
If you must use an explicit type conversion, use an appropriate named cast (e.g., static_cast) for a more precise statement of what you are trying to do.
Minimize the use of arrays and C-style strings.
To give a C++ function C linkage (so that it can be called from a C program fragment) or to allow a C function to be called from a C++ program fragment, declare it extern "C" .
extern "C" double sqrt(double);
Only one function of a given name in a scope can have C linkage (because C doesn’t allow function overloading).
Use constructors to establish invariants
Use constructor/destructor pairs to simplify resource management (RAII)
Avoid ‘‘naked’’ new and delete
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
Use unique_ptr to reference objects of polymorphic type
Use shared_ptr to reference shared objects, that is, objects without a single owner that is responsible for their destruction
Use templates to maintain static type safety (eliminate casts) and avoid unnecessary use of class hierarchies
Prefer named casts, such as static_cast over C-style casts
In C++, there are no implicit conversions from int s to enumerations; use explicit type conversion where necessary
Use extern "C" when declaring C functions;
Prefer string over C-style strings (direct manipulation of zero-terminated arrays of char )
Prefer iostream s over stdio
Prefer containers (e.g., vector ) over built-in arrays