MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Creating manipulators

(Note: This section contains some material that will not be introduced until later chapters.) Sometimes you’d like to create your own manipulators, and it turns out to be remarkably simple. A zero-argument manipulator like endl is simply a function that takes as its argument an ostream reference (references are a different way to pass arguments, discussed in Chapter 9). The declaration for endl is

ostream& endl(ostream&);

Now, when you say:

cout << “howdy” << endl;

the endl produces the address of that function. So the compiler says “is there a function I can call that takes the address of a function as its argument?” There is a pre-defined function in Iostream.h to do this; it’s called an applicator. The applicator calls the function, passing it the ostream object as an argument.

You don’t need to know how the applicator works to create your own manipulator; you only need to know the applicator exists. Here’s an example that creates a manipulator called nl that emits a newline without flushing the stream:

//: C18:nl.cpp
// Creating a manipulator
#include <iostream>
using namespace std;

ostream& nl(ostream& os) {
  return os << '\n';
}

int main() {
  cout << "newlines" << nl << "between" << nl
       << "each" << nl << "word" << nl;
} ///:~ 

The expression

os << '\n';

calls a function that returns os, which is what is returned from nl.[48]

People often argue that the nl approach shown above is preferable to using endl because the latter always flushes the output stream, which may incur a performance penalty.

Effectors

As you’ve seen, zero-argument manipulators are quite easy to create. But what if you want to create a manipulator that takes arguments? The iostream library has a rather convoluted and confusing way to do this, but Jerry Schwarz, the creator of the iostream library, suggests [49] a scheme he calls effectors. An effector is a simple class whose constructor performs the desired operation, along with an overloaded operator<< that works with the class. Here’s an example with two effectors. The first outputs a truncated character string, and the second prints a number in binary (the process of defining an overloaded operator<< will not be discussed until Chapter 10):

//: C18:Effector.txt
// (Should be "cpp" but I can't get it to compile with
// My windows compilers, so making it a txt file will
// keep it out of the makefile for the time being)
// Jerry Schwarz's "effectors"
#include<iostream>
#include <cstdlib>
#include <string>
#include <climits> // ULONG_MAX
using namespace std;

// Put out a portion of a string:
class Fixw {
  string str;
public:
  Fixw(const string& s, int width)
    : str(s, 0, width) {}
  friend ostream& 
  operator<<(ostream& os, Fixw& fw) {
    return os << fw.str;
  }
};

typedef unsigned long ulong;

// Print a number in binary:
class Bin {
  ulong n;
public:
  Bin(ulong nn) { n = nn; }
  friend ostream& operator<<(ostream&, Bin&);
};

ostream& operator<<(ostream& os, Bin& b) {
  ulong bit = ~(ULONG_MAX >> 1); // Top bit set
  while(bit) {
    os << (b.n & bit ? '1' : '0');
    bit >>= 1;
  }
  return os;
}

int main() {
  char* string =
    "Things that make us happy, make us wise";
  for(int i = 1; i <= strlen(string); i++)
    cout << Fixw(string, i) << endl;
  ulong x = 0xCAFEBABEUL;
  ulong y = 0x76543210UL;
  cout << "x in binary: " << Bin(x) << endl;
  cout << "y in binary: " << Bin(y) << endl;
} ///:~ 

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, and inserts it into the first argument, the ostream, then returns the ostream so 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 is passed to operator<<. The effect is that of a manipulator with arguments.

The Bin effector relies on the fact that shifting an unsigned number to the right shifts zeros into the high bits. ULONG_MAX (the largest unsigned long value, from the standard include file <climits> ) is used to produce a value with the high bit set, and this value is moved across the number in question (by shifting it), masking each bit.

Initially 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 (covered in Chapter XX), this problem is eliminated.


[48] Before putting nl into a header file, you should make it an inline function (see Chapter 7).

[49] In a private conversation.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru