avatar

Catalog
Modern C++ Programming Cookbook Notes 7: Stream, Robustness and Idioms

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 and std::noboolalpha to toggle the display of bool value between true (false) and 1(0).
    • std::left and std::right to affect the alignment of the output (usually cooperate with std::setw).
    • std::fixed, std::setprecision and std::defaultfloat to affect the output of floating-point types.
    • std::dec, std::oct and std::hex to change the base used for integral types.
    • std::setw and std::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 and std::bad_*, which are derived from std::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 to noexcept while noexcept(false) means no exception specification.

  • As an operator, it can be used to check whether a function is noexcept at compile time:

    c++
    1
    2
    void f() noexcept;
    std::cout << noexcept(f()) << std::endl; // 1

    Note 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 as noexcept, the program will exit immediately with a call to std::terminate(). On the other hand, a noexcept function can bring some performance optimizations.

    For example, push_back() method of std::vector will use move constructor if it’s noexcept 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 of noexcept.

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 as constexpr).
  • When applying to functions, they are implicitly inline but not const 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
    12
    std::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
    14
    std::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
    13
    class 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
    25
    class 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
    12
    template <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> and Base<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
    17
    template <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>;
    };
Author: Gusabary
Link: http://gusabary.cn/2020/11/22/Modern-C++-Programming-Cookbook-Notes/Modern-C++-Programming-Cookbook-Notes-7/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment