Every stream object has an internal buffer for which it acts as a front end. Such buffers are exchangeable. If we have a stream object, s, and want to save its buffer into a variable, a, and install a new buffer, b, this looks like the following: a = s.rdbuf(b). Restoring it can be simply done with s.rdbuf(a).
This is exactly what we did in this recipe. Another cool thing is that we can stack those redirect_cout_region helpers:
{
cout << "print to standard outputn";
redirect_cout_region la {"a.txt"};
cout << "print to a.txtn";
redirect_cout_region lb {"b.txt"};
cout << "print to b.txtn";
}
cout << "print to standard output againn";
This works because objects are destructed in the opposite order of their construction. The concept behind this pattern that uses the tight coupling between construction and destruction of objects is called Resource Acquisition Is Initialization (RAII).
There is one really important thing that should be mentioned--the initialization order of the member variables of the redirect_cout_region class:
class redirect_cout_region {
using buftype = decltype(cout.rdbuf());
ofstream ofs;
buftype buf_backup;
public:
explicit
redirect_cout_region(const string &filename)
: ofs{filename},
buf_backup{cout.rdbuf(ofs.rdbuf())}
{}
...
As we can see, the member, buf_backup, is constructed from an expression that depends on ofs. This obviously means that ofs needs to be initialized before buf_backup. Interestingly, the order in which these members are initialized does not depend on the order of the initializer list items. The initialization order only depends on the order of the member declarations!