Copy Link
Add to Bookmark
Report

C++ Newsletter/Tutorial Issue 7

eZine's profile picture
Published in 
CPPNL
 · 2 years ago

Issue #007
March, 1996

Contents

  • Introduction to Stream I/O Part 2 - Formatting and Manipulators
  • Using C++ as a Better C Part 7 - Inline Functions
  • Programming Quiz
  • Performance - Stream I/O Output


INTRODUCTION TO STREAM I/O PART 2 - FORMATTING AND MANIPULATORS

In this issue we will talk further about stream I/O. An excellent book on the subject is Steve Teale's "C++ IOStreams Handbook" (Addison-Wesley, $40). It has 350 pages with many examples and thoroughly covers I/O streams. It should be noted that I/O streams are undergoing revision as part of the ANSI/ISO standardization process. The examples we present are based on C++ headers and libraries in common use.

One obvious question about stream I/O is how to do formatting. A simple operation like:

    int n = 37; 

cout << n;


is equivalent to:

    printf("%d", n);


that is, no special formatting is done.

But what if you want to say:

    printf("%08d", n);


displaying n in a field 8 wide with leading 0s? Such an operation would be performed by saying:

    #include <iostream.h> 
#include <iomanip.h>

/* stuff */

cout << setfill('0') << setw(8) << n;


setfill() and setw() are examples of I/O stream manipulators. A manipulator is a data object of a type known to I/O streams, that allows a user to change the state of a stream. We'll see how manipulators are implemented in a moment.

The operation illustrated here first sets the fill character to '0' and then the width of the field to 8 and then outputs the number. Some I/O stream settings like the fill character and left/right justification persist, but the width is reset after each output item.

A similar way of changing stream state is to use regular member function calls. For example,

    cout.setf(ios::left); 

cout << setfill('0') << setw(8) << n;


produces output:

    37000000


with left justification.

In this example, you see "ios::left" mentioned. What is ios? ios is the base class of the streams class hierarchy. In the implementation used here, the hierarchy is:

    class ios { /* stuff */ }; 

class ostream : public ios { /* stuff */ }

class ostream_withassign : public ostream { /* stuff */ }


and cout is an object instance of ostream_withassign. That is, there is a base class (ios), and the output streams class (ostream) derives or inherits from it, and ostream_withassign derives from ostream. Chapter #10 of Teale's book mentioned above discusses the rationale for the ostream_withassign class.

A statement like:

    cout.setf(ios::left);


calls the member function setf() inherited from the ios class, to set flags for the stream. ios::left is an enumerator representing a particular flag value.

How can you design your own manipulators? A simple example is as follows:

    #include <iostream.h> 
#include <iomanip.h>

ostream& dash(ostream& os)
{
return os << " -- ";
}

main()
{
cout << "xxx" << dash << "yyy" << endl;

return 0;
}


We define a manipulator called "dash" that inserts a dash into an output stream. This is followed by the output of more text and then a builtin manipulator ("endl") is called. endl inserts a newline character and flushes the output buffer. We will say more about endl later in the newsletter.

Manipulators are in fact pointers to functions, and they are implemented via a couple of hooks in iostream.h:

    ostream& operator<<(ostream& (*)(ostream&)); 
ostream& operator<<(ios& (*)(ios&));


These operators are member functions of class ostream. They will accept either a pointer to function that takes an ostream& or a pointer to function that takes an ios&. The former would be used for actual output, the latter for setting ios flags as discussed above.

USING C++ AS A BETTER C PART 7 - INLINE FUNCTIONS

Suppose that you wish to write a function in C to compute the maximum of two numbers. One way would be to say:

    int max(int a, int b) 
{
return (a > b ? a : b);
}


But calling a frequently-used function can be a bit slow, and so you instead use a macro:

    #define max(a, b) ((a) > (b) ? (a) : (b))


The extra parentheses are required to handle cases like:

    max(a = b, c = d)


This approach can work pretty well. But it is error-prone due to the extra parentheses and also because of side effects like:

    max(a++, b++)


An alternative in C++ is to use inline functions:

    inline int max(int a, int b) 
{
return (a > b ? a : b);
}


Such a function is written just like a regular C or C++ function. But it IS a function and not simply a macro; macros don't really obey the rules of C++ and therefore can introduce problems. Note also that one could use C++ templates to write this function, with the argument types generalized to any numerical type.

If an inline function is a member function of a C++ class, there are a couple of ways to write it:

    class A { 
public:
void f() { /* stuff */ } // "inline" not needed
};


or:

    class A { 
public:
inline void f();
};

inline void A::f()
{
/* stuff */
}


The second style is often a bit clearer.

The "inline" keyword is merely a hint to the compiler or development environment. Not every function can be inlined. Some typical reasons why inlining is sometimes not done include:

  • the function calls itself, that is, is recursive
  • the function contains loops such as for(;;) or while()
  • the function size is too large

Most of the advantage of inline functions comes from avoiding the overhead of calling an actual function. Such overhead includes saving registers, setting up stack frames, and so on. But with large functions the overhead becomes less important.

Inline functions present a problem for debuggers and profilers, because the function is expanded at the point of call and loses its identity. A compiler will typically have some option available to disable inlining.

Inlining tends to blow up the size of code, because the function is expanded at each point of call. The one exception to this rule would be a very small inline function, such as one used to access a private data member:

    class A { 
int x;
public:
int getx() {return x;}
};


which is likely to be both faster and smaller than its non-inline counterpart.

A simple rule of thumb when doing development is not to use inline functions initially. After development is mostly complete, you can profile the program to see where the bottlenecks are and then change functions to inlines as appropriate.

Here's a complete program that uses inline functions as part of an implementation of bit maps. Bit maps are useful in storing true/false values efficiently. Note that in a couple of places we could use the new bool fundamental type in place of ints. Also note that this implementation assumes that chars are 8 bits in width; there's no fundamental reason they have to be (in Java the Unicode character set is used and chars are 16 bits).

This example runs about 50% faster with inlines enabled.

    #include <assert.h> 
#include <stdlib.h>
#include <string.h>

//#define inline

class Bitmap {
typedef unsigned long UL; // type of specified bit num
UL len; // number of bits
unsigned char* p; // pointer to the bits
UL size(); // figure out bitmap size
public:
Bitmap(UL); // constructor
~Bitmap(); // destructor
void set(UL); // set a bit
void clear(UL); // clear a bit
int test(UL); // test a bit
void clearall(); // clear all bits
};

// figure out bitmap size
inline Bitmap::UL Bitmap::size()
{
return (len - 1) / 8 + 1;
}

// constructor
inline Bitmap::Bitmap(UL n)
{
assert(n > 0);
len = n;
p = new unsigned char[size()];
assert(p);
clearall();
}

// destructor
inline Bitmap::~Bitmap()
{
delete [] p;
}

// set a bit
inline void Bitmap::set(UL bn)
{
assert(bn < len);
p[bn / 8] |= (1 << (bn % 8));
}

// clear a bit
inline void Bitmap::clear(UL bn)
{
assert(bn < len);
p[bn / 8] &= ~(1 << (bn % 8));
}

// test a bit, return non-zero if set
inline int Bitmap::test(UL bn)
{
assert(bn < len);
return p[bn / 8] & (1 << (bn % 8));
}

// clear all bits
inline void Bitmap::clearall()
{
memset(p, 0, size());
}

#ifdef DRIVER
main()
{
const unsigned long N = 123456L;
int i;
long j;
int k;
int r;

for (i = 1; i <= 10; i++) {
Bitmap bm(N);

// set all bits then test

for (j = 0; j < N; j++)
bm.set(j);
for (j = 0; j < N; j++)
assert(bm.test(j));

// clear all bits then test

for (j = 0; j < N; j++)
bm.clear(j);
for (j = 0; j < N; j++)
assert(!bm.test(j));

// run clearall() then test

bm.clearall();
for (j = 0; j < N; j++)
assert(!bm.test(j));

// set and clear random bits

k = 1000;
while (k-- > 0) {
r = rand() & 0xffff;
bm.set(r);
assert(bm.test(r));
bm.clear(r);
assert(!bm.test(r));
}
}

return 0;
}
#endif


PROGRAMMING QUIZ

How many lines does this program output?

    #include <iostream.h> 

main()
{
unsigned int n;

n = 10;

do {
cout << "xxx" << "\n";
n--;
} while (n >= 0);

return 0;
}


Answer in the next issue.


PERFORMANCE

In issue #006 we talked about stream I/O, and an example like this was shown:

    cout << x << "\n";


A couple of people wrote. One said that:

    cout << x << endl;


was preferable, while another said:

    cout << x << '\n';


would be a better choice on performance grounds, that is, output a single character instead of a C string containing a single character.

Using one popular C++ compiler (Borland C++ 4.52), and outputting 100K lines using these three methods, the running times in seconds are:

    "\n"            1.9 

'\n' 1.3

endl 13.2


Outputting a single character is a little simpler than outputting a string of characters, so it's a bit faster.

Why is endl much slower? It turns out that it has different semantics. Besides adding a newline character like the other two forms do, it also flushes the output buffer. On a UNIX-like system, this means that ultimately a write() system call is done for each line, an expensive operation. Normally, output directed to a file is buffered in chunks of size 512 or 1024 or similar.

The Borland compiler has a #define called _BIG_INLINE_ in iostream.h that was enabled to do more inlining and achieve the times listed here.

Does this sort of consideration matter very much? Most of the time, no. If you're doing interactive I/O, it is best to write in the style that is plainest to you and others. If, however, you're writing millions of characters to files, then you ought to pay attention to an issue like this.

Note also that there's no guarantee that performance characteristics of stream I/O operations will be uniform across different compilers. It's probably true in most cases that outputting a single character is cheaper than outputting a C string containing a single character, but it doesn't have to be that way.


ACKNOWLEDGEMENTS

Thanks to Terry Rudd, Jonathan Schilling, and Clay Wilson for help with proofreading.


SUBSCRIPTION INFORMATION / BACK ISSUES

To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body:

subscribe c_plus_plus

Back issues are available via FTP from:

rmii.com /pub2/glenm/newslett

or on the Web at:

http://www.rmii.com/~glenm

-------------------------

Copyright (c) 1996 Glen McCluskey. All Rights Reserved.

This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom.

Glen McCluskey & Associates
Professional C++ Consulting
Internet: glenm@glenmccl.com
Phone: (800) 722-1613 or (970) 490-2462
Fax: (970) 490-2463
FTP: rmii.com /pub2/glenm/newslett (for back issues)
Web: http://www.rmii.com/~glenm

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT