Выбрать главу

ostream& endl(ostream&);

Now, when you say:.

cout << "howdy" << endl;

the endl produces the address of that function. So the compiler asks, "Is there a function I can call that takes the address of a function as its argument?" Predefined functions in <iostream> do this; they’re called applicators (because they apply a function to a stream). The applicator calls its function argument, passing it the ostream object as its argument. You don’t need to know how applicators work to create your own manipulator; you only need to know that they exist. Nonetheless, they’re simple. Here’s the (simplified) code for an ostream applicator:

ostream& ostream::operator<<(ostream& (*pf)(ostream&)) {

  return pf(*this);

}

The actual definition is a little more complicated since it involves templates, but this code illustrates the technique. When a function such as *pf (that takes a stream parameter and returns a stream reference) is inserted into a stream, this applicator function is called, which in turn executes the function to which pf points. Applicators for ios_base, basic_ios, basic_ostream, and basic_istream are predefined in the standard C++ library.

To illustrate the process, here’s a trivial example that creates a manipulator called nl that is equivalent to just inserting a newline into a stream (i.e., no flushing of the stream occurs, as with endl):.

//: C04: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;

} ///:~

When you insert nl into an output stream, such as cout, the following sequence of calls ensues:

cout.operator<<(nl) è nl(cout)

The expression

os << '\n';

inside nl( ) calls ostream::operator(char), which of course returns the stream, which is what is ultimately returned from nl( ).[45] 

Effectors

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 give in to the temptation. The smanip type is implementation-dependent, so using it would not be 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.[46] 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 of course concatenated into one 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.

вернуться

45

Before putting nl into a header file, make it an inline function (see Chapter 7).

вернуться

46

Jerry Schwarz is the designer of iostreams.