Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
As you ve seen, zero-argument manipulators are easy to
create. But what if you want to create a manipulator that takes arguments? If
you inspect the <iomanip> header, you ll see a type called smanip, which is what the manipulators with arguments return. You might be tempted to
somehow use that type to define your own manipulators, but don t do it. The smanip
type is implementation-dependent and thus not portable. Fortunately, you can
define such manipulators in a straightforward way without any special
machinery, based on a technique introduced by Jerry Schwarz, called an effector. An effector is
a simple class whose constructor formats a string representing the desired
operation, along with an overloaded operator<< to insert that
string into a stream. Here s an example with two effectors. The first outputs a
truncated character string, and the second prints a number in binary.
//: C04:Effector.cpp
// Jerry Schwarz's "effectors."
#include <cassert>
#include <limits> // For max()
#include <sstream>
#include <string>
using namespace std;
// Put out a prefix of a string:
class Fixw {
string str;
public:
Fixw(const string& s, int width) : str(s, 0,
width) {}
friend ostream& operator<<(ostream& os,
const Fixw& fw) {
return os << fw.str;
}
};
// Print a number in binary:
typedef unsigned long ulong;
class Bin {
ulong n;
public:
Bin(ulong nn) { n = nn; }
friend ostream& operator<<(ostream& os,
const Bin& b) {
const ulong ULMAX =
numeric_limits<ulong>::max();
ulong bit = ~(ULMAX >> 1); // Top bit set
while(bit) {
os << (b.n & bit ? '1' : '0');
bit >>= 1;
}
return os;
}
};
int main() {
string words = "Things that make us happy, make
us wise";
for(int i = words.size(); --i >= 0;) {
ostringstream s;
s << Fixw(words, i);
assert(s.str() == words.substr(0, i));
}
ostringstream xs, ys;
xs << Bin(0xCAFEBABEUL);
assert(xs.str() ==
"1100""1010""1111""1110""1011""1010""1011""1110");
ys << Bin(0x76543210UL);
assert(ys.str() ==
"0111""0110""0101""0100""0011""0010""0001""0000");
} ///:~
The constructor for Fixw creates a shortened copy of
its char* argument, and the destructor releases the memory created for
this copy. The overloaded operator<< takes the contents of its
second argument, the Fixw object, inserts it into the first argument,
the ostream, and then returns the ostream so that it can be used
in a chained expression. When you use Fixw in an expression like this:
cout << Fixw(string, i) << endl;
a temporary object is created by the call to the Fixw
constructor, and that temporary object is passed to operator<<.
The effect is that of a manipulator with arguments. The temporary Fixw
object persists until the end of the statement.
The Bin effector relies on the fact that shifting an
unsigned number to the right shifts zeros into the high bits. We use numeric_limits<unsigned long>::max( ) (the largest unsigned long
value, from the standard header <limits>) to produce a value with the high bit set, and this value is moved across the number in question (by shifting it to
the right), masking each bit in turn. We ve juxtaposed string literals in the
code for readability; the separate strings are concatenated into a single
string by the compiler.
Historically, the problem with this technique was that once
you created a class called Fixw for char* or Bin for unsigned
long, no one else could create a different Fixw or Bin class
for their type. However, with namespaces, this problem is eliminated. Effectors
and manipulators aren t equivalent, although they can often be used to solve
the same problem. If you find that an effector isn t enough, you will need to
conquer the complexity of manipulators.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |