Chapter 7 Working with files and streams
7.4 Using I/O manipulators to control the output of a stream
- Standard provides many helper functions called manipulators to control the input and output of a stream.
- Listed here are some common used manipulators:
std::boolalpha
andstd::noboolalpha
to toggle the display of bool value betweentrue
(false
) and 1(0).std::left
andstd::right
to affect the alignment of the output (usually cooperate withstd::setw
).std::fixed
,std::setprecision
andstd::defaultfloat
to affect the output of floating-point types.std::dec
,std::oct
andstd::hex
to change the base used for integral types.std::setw
andstd::setfill
to format the output with some custom styles.
- Note that all the manipulators except
std::setw
has an effect on the stream and all the following I/O operations until encountering another manipulator. (std::setw
just affects the next single operation)
Chapter 8 Leveraging Threading and Concurrency
Chapter 9 Robustness and Performance
9.1 Using exceptions for error handling
There are some recommended practice when dealing with exceptions:
- throw exceptions by value and catch it by reference (in most cases, const reference).
- arrange multiple catch statements in the order from most derived to the base, and
catch(...)
as the last one potentially. - use
throw;
directly in the catch statement to rethrow the exception.
When we combine the concept of both exception and OOP, there is a key point to mention: Use exceptions to indicate errors in constructors and don’t let exceptions leave destructors.
The reason is simple: when an exception occurs, the stack is unwound from the point where the exception was thrown to the point where it will be handled. This unwinding process involves destruction of all local objects in these stack frames. If the destructor of one object throws an exception, another unwinding process should begin, which conflicts the one already under way.
And one thing worth noting is that after an exception is thrown from the constructor, the destructor won’t get invoked.
Standard library provides some exceptions like
std::logic_error
,std::runtime_error
andstd::bad_*
, which are derived fromstd::exception
.
9.2 Using noexcept for functions that do not throw
C++11 introduces
noexcept
keyword, which can be used as either a specifier and an operator.As a specifier, it applies to a function, indicating that function doesn’t throw any exception. Optionally,
noexcept
can take a parameter which can be evaluated to a bool value. To be precise,noexcept(true)
is equivalent tonoexcept
whilenoexcept(false)
means no exception specification.As an operator, it can be used to check whether a function is
noexcept
at compile time:c++1
2void f() noexcept;
std::cout << noexcept(f()) << std::endl; // 1Note that the expression used for check is not evaluated.
Be careful to specify a function as
noexcept
. On the one hand, if exceptions leave a function that is specified asnoexcept
, the program will exit immediately with a call tostd::terminate()
. On the other hand, anoexcept
function can bring some performance optimizations.For example,
push_back()
method ofstd::vector
will use move constructor if it’snoexcept
since the method has a strong exception guarantee (which means the state of program will stay the same after an exception is thrown, i.e. commit-or-rollback semantic). And actually indicating that move constructors don’t throw is an important scenario ofnoexcept
.
9.4 Creating compile-time constant expressions
constexpr
can be used to create compile-time constant expressions. It can apply to variables, functions, constructors and so on.- When applying to variables, they should be literal type (e.g.
std::string
cannot be declared asconstexpr
). - When applying to functions, they are implicitly
inline
but notconst
since C++14. - When applying to constructors, we can potentially create
constexpr
objects of that class.
Chapter 10 Implementing Patterns and Idioms
10.1 Avoiding repetitive if…else statements in factory methods
To avoid repetitive if…else statements in factory methods, we can certainly use polymorphism. However, there is another trick which uses a map:
c++1
2
3
4
5
6
7
8
9
10
11
12std::shared_ptr<Image> Create(std::string_view type) {
static std::map<std::string, std::function<std::shared_ptr<Image>()>> mapping {
{ "bmp", []() {return std::make_shared<BitmapImage>(); } },
{ "png", []() {return std::make_shared<PngImage>(); } },
{ "jpg", []() {return std::make_shared<JpgImage>(); } }
};
auto it = mapping.find(type.data());
if (it != mapping.end())
return it->second();
return nullptr;
}What’s more, we can replace the hardcoded string indicating the type with
type_info
:c++1
2
3
4
5
6
7
8
9
10
11
12
13
14std::shared_ptr<Image> Create(std::type_info const & type) {
static std::map<std::type_info const *, std::function<std::shared_ptr<Image>()>> mapping {
{&typeid(BitmapImage),[](){return std::make_shared<BitmapImage>();}},
{&typeid(PngImage), [](){return std::make_shared<PngImage>();}},
{&typeid(JpgImage), [](){return std::make_shared<JpgImage>();}}
};
auto it = mapping.find(&type);
if (it != mapping.end())
return it->second();
return nullptr;
}
factory.Create(typeid(PngImage));
10.2 Implementing the pimpl idiom
Pimpl idiom, which stands for pointer to implementation, is a technique for better separation between interface and implementation, to my understanding.
Typically, putting interfaces in header files and implementations in source files is already a practice of separation of them. However, when we change some internal state (like private members), the header still needs to recompile although the change is transparent to the client. Now with pimpl idiom, we can move almost all these internal state to another class called pimpl class and in the original public class, just leave interfaces to avoid recompilation when modifying such internal state.
Practically, public class has all the interfaced defined and a pointer to pimpl class. All the implementations of public class method forward to pimpl class.
However, the benefits of pimpl idiom doesn’t come for free. It’s more difficult to read of course. And also, it cannot apply to protected members and private virtual functions.
10.3 Implementing the named parameter idiom
C++ supports only positional parameters (some other language also supports named parameter like Python but C++ is still the best programming language!!). However we can implement named parameter technique on our own.
To implement that, we need to create a class dedicated for the parameter passing, whose methods share the same name with parameter name and, the most important, return value is the reference to itself (
return *this
). So we can construct the argument list like a chain:c++1
control c(control_properties(1044).visible(true).height(20).width(100));
10.4 Separating interfaces and implementations with the non-virtual interface idiom
Non-virtual interface (NVI) idiom is to make all public interfaces non-virtual and private implementations virtual. Essentially it adds an indirection for better separation and control of interfaces:
c++1
2
3
4
5
6
7
8
9
10
11
12
13class Base {
protected:
virtual void initialize_impl() {}
public:
void initialize() {
initialize_impl();
}
};
class Derived : public Base {
protected:
virtual void initialize_impl() {}
};
10.5 Handling friendship with the attorney-client idiom
If a function or class is declared as friend of another class, all private members of it will be exposed to its friend, which is not always what we want. This can be handled by attorney-client idiom, which make it possible to expose part of private members to friends. The principle is very simple: another indirection, which is called attorney:
c++1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Client {
int data_1;
int data_2;
void action1() {}
void action2() {}
friend class Attorney;
};
class Attorney {
static inline void run_action1(Client& c) {
c.action1();
}
static inline int get_data1(Client& c) {
return c.data_1;
}
friend class Friend;
};
class Friend {
public:
void access_client_data(Client& c) {
Attorney::run_action1(c);
auto d1 = Attorney::get_data1(c);
}
};
10.6 Static polymorphism with the curiously recursive template pattern
Polymorphism based on virtual functions are called runtime polymorphism or late binding. We can simulate it at compile time with static polymorphism (or early binding), using the mechanism curiously recursive template pattern (CRTP):
c++1
2
3
4
5
6
7
8
9
10
11
12template <class T>
class Base {
public:
void draw() {
static_cast<T*>(this)->paint();
}
};
class Derived : public Base<Derived> {
public:
void paint() {}
}Note that there is a pitfall when using CRTP: different derived classes with the same base (e.g.
Base<A>
andBase<B>
) have different types (unlike the runtime polymorphism) so we cannot store them in a homogeneous container such as vector or list. Of course there is a workaround: inherit the base class from another class and use that base-base class as the template argument of the container.
10.7 Implementing a thread-safe singleton
After C++11, the compiler guarantees that objects with static storage will be initialized only once. Remember the threes ways to initialize objects thread-safely in Section 2.2, Concurrency with Modern C++?
So implementing a thread-safe singleton is quite easy. We can even combine it with CRTP introduced in last section to create a generic singleton base class:
c++1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17template <class T>
class SingletonBase {
protected:
SingletonBase() {}
public:
SingletonBase(SingletonBase const &) = delete;
SingletonBase& operator=(SingletonBase const&) = delete;
static T& instance() {
static T single;
return single;
}
};
class Single : public SingletonBase<Single> {
Single() {}
friend class SingletonBase<Single>;
};