Site hosted by Angelfire.com: Build your free website today!

C++ In Depth

Bit Handling in C+


The Standard C++ Library

When ANSI Committee X3J16 convened in 1989, it planned to at least standardize the C++ library stream classes. At the close of the third meeting in July 1990 the Library Working Group emerged with more specific direction: 

1) standardize the stream classes

2) define mechanisms to support language features such as the new and delete operators and exceptions

 3) define the relationship between the standard C library and the C++ library and 

4) define a standard string class. 

The overall charter of any standards committee is to standardize existing practice. It is not feasible, however, for the committee to design a comprehensive object-oriented library. A large group of volunteers cannot accomplish what a small, focused department can (plus the pay is not as good). It would be more reasonable to standardize a small number of popular, low-level classes as building blocks for a wide spectrum of applications. To determine the existing industry practice in development of libraries, in June 1991 I volunteered to review the libraries which a number of vendors had submitted to the committee. All submissions offered at least one string class and a number of container classes, such as lists, vectors, queues, stacks, and sets. Many vendors' libraries also supported bit sets. Now, two years later, the following classes are part of the standard C++ library (in addition to streams and language support):

Class     Description
string  dynamic array of characters
wstring   dynamic array of wide characters
dynarray  dynamic array of anything (vector)
bits   fixed-length bitset
bitstring dynamic-length bit string


The string class, which started with contributions from Aron Insinga of Digital, and was completed through the efforts of Uwe Steinmueller of Siemens Nixdorf and Pete Becker at Borland, supports most of C's string.h functionality on dynamically-sized arrays of bytes. Beman Dawes adapted the string class to handle arrays of wide characters, of type wchar_t. (FYI: this is a keyword representing a distinct, overloadable type in C++). Uwe also designed the dynarray class.Chuck Ellison was interested in improving on C's awkward bitwise support, and designed the last two classes. In this article I will discuss the bitstring class.

Bit Sets

C's bitwise operators allow you to process an integer as an ordered set of bits. This ability is useful for system interface operations, which often interpret individual bits as flags. If you want more bits than can fit in an unsigned long, however, you're out of luck. An extended version of such a bit array can come in handy, though. 

The first bitset class proposal submitted to X3J16 tried to meet all of these needs with a single class. After much discussion and little agreement among working members, the committee realized that existing practice called for two distinct abstractions: fixed-length bit sets (for a collection of flags, say) and bit strings (i.e., dynamic bit arrays). One year and three committee meetings later, these abstractions became class bits and bitstring, respectively.

Class bitstring

The bitstring class provides much of the same functionality as the string class (see the sidebar "A Simple String Class"). bitstring supports dynamically-sized bit strings with operations such as insert, remove, replace, and concatenate, as well as the basic bit operations — set, reset, test, and toggle  Since a bitstring is in fact a string, the bit numbering begins with bit 0 on the left, and increases as you move to the right (this is the reverse of numeric bitfields, which are numbered from right to left). I can now easily implement a picklist by embedding a bitstring:

class Picklist : public Popmenu
{
    bitstring picks;
    // . . .
public:
    Picklist(.., nitems,..)
        : picks(0,nitems), .. {}
    // . . .
};
When the user highlights the nth entry, I set bit n in the bitstring:

picks.set(n);

Member Function Descriptions

This section is a modified excerpt from the official proposal which describes the semantics of each member function. Since the library group is still deciding how to integrate exceptions into the standard library, I just mention them briefly here. The names and uses of exceptions are subject to change. I use assertions in place of exceptions in the implementation  . Throughout the rest of this article, the value NPOS, which stands for "not a position," is the largest possible value of type size_t, which is equivalent to (size_t)(-1). A bitstring can hold up to NPOS - 1 bits.

1.0 Constructors

Synopsis

bitstring()
bitstring(unsigned long n, size_t nbits)
bitstring(const string& s)
bitstring(const bitstring& b)

1.1 Constructor bitstring()

Description

Creates a bit string of length zero.

1.2 Constructor bitstring(unsigned long n, size_t nbits)

Description

Creates a bit string at least nbits long, initialized with the contents of n. The constructor drops no significant bits, i.e., this->length == max(nbits, N+1), where N is the bit position of the highest-order one bit. The constructor pads the higher order bits with zeroes if necessary, to fill out nbits bits. The common usage of this constructor would be to initialize a bitstring of a certain length with zeroes, e.g.,

bitstring x(0,16);
Be aware when using a non-zero value initializer that the bits will be "reversed." For example, the bit pattern for the decimal number 21025 is 0101001000100001, but the output from the following code

bitstring x(21025,16);
cout << x << endl;
is 1000010001001010.

1.3 Constructor bitstring(const string& s)

Description

Reads bit characters ('1's and '0's) from s and sets the corresponding bits in the new bitstring. The first bit read corresponds to bit 0.

Exceptions

Throws invalid_argument if any character in the string is not a '1' or '0'.

1.4 Constructor bitstring(const bitstring& b)

Description

The usual copy constructor.

 

2.0 Destructor bitstring()

Description

Frees resources and destroys the object.

3.0 Other Member Functions

3.1 Function string to_string()

Description

Creates a string of '1's and '0's representing the contents of *this. The first (left-most) character is bit 0.

Returns

The temporary string of '1's and '0's.

3.2 Function bitstring& operator=(const bitstring&)

Description

Standard assignment operator.

Returns

A reference to *this.

3.3 Function int operator==(const bitstring&b) const

Description

Compares *this to b for equality. Two bitstrings are equal if and only if they have the same number of bits and their bit patterns are identical.

Returns

Non-zero if the bitsets are equal, zero otherwise.

3.4 Function int operator!=(const bitstring&b) const

Description

Compares *this to b for inequality. Equivalent to !operator==(b);

Returns

Zero if the bitsets are equal, non-zero otherwise.

3.5 Functions set

Synopsis

bitstring& set(size_t n, int val = 1)
bitstring& set()

Description

These functions set one or more bits. The function set(size_t, int can also reset a bit, depending on the contents of val.

3.5.1 Function bitstring& set (size_t n, int val = 1)

Description

Sets the nth bit if val is non-zero, otherwise resets the bit. Appends a bit if n == length.

Returns

A reference to *this.

Exceptions

Throws out_of_range if n > length.

3.5.2 Function bitstring& set()

Description

Sets all bits,

Returns

A reference to *this.

3.6 Functions reset

Synopsis

bitstring& reset(size_t n)
bitstring& reset()

Description

These functions reset one or more bits.

3.6.1 Function bitstring& reset(size_t n)

Description

Resets the nth bit. Appends the bit if n == length.

Returns

A reference to *this.

Exceptions

Throws out_of_range if n > length.

3.6.2 Function bitstring& reset()

Description

Resets all bits.

Returns

A reference to *this.

3.7 Functions toggle

Synopsis

bitstring& toggle(size_t n)
bitstring& toggle()

Description

These functions toggle one or more bits.

3.7.1 Function bitstring& toggle(size_t n)

Description

Toggles the nth bit.

Returns

A reference to *this.

Exceptions

Throws out_of_range if n >= length.

3.7.2 Function bitstring& toggle()

Description

Toggles all bits.

Returns

A reference to *this.

3.8 Function int test(size_t n) const

Description

Tests if bit n is set.

Returns

Non-zero if the bit is set, zero otherwise.

Exceptions

Throws out_of_range if n >= length.

3.9 Function int any() const

Description

Tests if any bits at all are set.

Returns

Zero if all bits are 0, non-zero otherwise.

3.10 Function int none() const

Description

Tests if no bits at all are set. Equivalent to !any.

Returns

One if all bits are 0, non-zero otherwise.

3.11 Function bits operator~() const

Description

Toggles all the bits of a copy of *this.

Returns

A toggled copy of *this.

3.12 Function size_t count() const

Description

Counts the number of bits that are set.

Returns

The number of 1 bits in the string.

3.13 Function bitstring& operator&=(const bitstring&b)

Description

Replaces the current object with the bitwise-AND of b and *this. This function behaves as if the shorter bitstring is filled with zeroes on the right, so the operands of the AND operation will be of equal length. The new length of the object is the maximum of the lengths of the old object and b.

Returns

A reference to *this.

3.14 Function bitstring& operator|=(const bitstring& b)

Description

Replaces the current object with the bitwise-OR of b and *this. This function behaves as if the shorter bitstring is filled with zeroes on the right, so the operands of the XOR operation will be of equal length. The new length of the object is the maximum of the lengths of the old object and b.

Returns

A reference to *this.

3.15 Function bitstring& operator^=(const bitstring& b)

Description

Replaces the current object with the bitwise-XOR of b and *this. This function behaves as if the shorter bitstring is filled with zeroes on the right, so the operands of the XOR operation will be of equal length. The new length of the object is the maximum of the lengths of the old object and b.

Returns

A reference to *this.

3.16 Function bitstring& operator>>=(size_t n)

Description

Modifies the current object by shifting *this right, by n bit positions. If n > length, then this function resets all bits. Shifting "right" by n means that bit n gets the value of bit 0, bit (n+1) gets bit 1, etc. This function resets any vacated bits.

Returns

A reference to *this.

3.17 Function bitstring& operator<<=(size_t n)

Description

Modifies the current object by shifting *this left by n bit positions. if n > length, then this function resets all bits. Shifting "left" by n means that bit 0 receives the value of bit n, bit 1 receives bit (n+1), etc.

Returns

A reference to *this.

3.18 Function bitstring operator>>(const bitstring& b, size_t n) const

Description

Shifts b right by n bit positions. Does not modify b. (See 3.16).

Returns

The shifted result in a temporary bitstring.

3.19 Function bitstring operator<<(const bitstring& b, size_t n) const

Description

Shifts b left by n bit positions. Does not modify b, (See 3.17).

Returns

The shifted result in a temporary bitstring.

3.20 Function bitstring& operator+= (constbitstring& b)

Description

Appends b destructively to *this.

Returns

A reference to *this.

Exceptions

Throws length_error if length >= NPOS - b. length.

3.21 Function bitstring& insert (size_t pos, const bitstring& b)

Description

Inserts b into *this starting at bit position pos. The bits of *this starting at pos follow the inserted copy of b. Appends b if pos == length.

Returns

A reference to *this.

Exceptions

Throws out_of_range if pos > length. Throws length_error if length >= NPOS - b.length.

3.22 Function bitstring& remove(size_t pos, size_t n = NPOS)

Description

Removes up to n bits from *this starting at bit position pos. Truncates, starting at position pos, if length - pos < n.

Returns

A reference to *this.

Exceptions

Throws out_of_range if pos > length.

3.24 Function bitstring&replace(size_t pos, size_t n, const bitstring& b)

Description

Equivalent to a remove followed by a replace in the obvious way. Included for convenience and efficiency.

Returns

A reference to *this.

Exceptions

Throws out_of_range if pos >= length. Throws length_error if the new length would be >= NPOS.

3.25 Function size_t find(int val, size_t pos = 0) const

Description

Searches forward, starting from position pos, for the first occurrence of a 0 bit (if val is zero) or a 1 bit (if val is non-zero).

Returns

The found bit position, if the search was successful, NPOS otherwise.

Exceptions

Throws out_of_range if pos > length.

3.26 Function size_t rfind(int val, size_t pos = NPOS) const

Description

Searches backwards from position pos (or from position length-1 if pos == NPOS) for the first occurrence of a 0 bit (if val is zero) or a 1 bit (if val is nonzero).

Returns

The found bit position, if the search was successful, NPOS otherwise.

Exceptions

Throws out_of_range if pos > length.

3.27 Function bitstring substr(size_t pos, size_t n = NPOS) const

Description

Creates a new bitstring consisting of the bit pattern starting at position pos. This function copies up to n bits or all bits to the end of the string, whichever number is smaller. If pos == length, this function creates an empty bitstring.

Returns

The new bitstring.

Exceptions

Throws out_of_range if pos > length.

3.28 Functions length()

Synopsis

size_t length()
size_t length(size_t n, int val = 0)

Description

These functions determine and/or modify the length of a bit-string.

3.28.1 Function size_t length() const

Description

Determines the number of bits in the string.

Returns

The number of bits.

3.28.2 Function size_t length(size_t n, int val = 0)

Description

Resizes (i.e., truncates or expands) the object so that this->length == n. If the length increases, this function extends the object on the right with 0s if val == 0, or with 1s otherwise.

Returns

The old length.

3.29 Function bitstring& trim()

Description

Truncates high-order 0 bits. If N is the highest-numbered 1 bit, then this->length becomes N+1.

Returns

A reference to *this.

4.0 Global Functions

4.1 Function ostream& operator<<(ostream& os, const bitstring& b)

Description

Sends the sequence of '1's and '0's corresponding to b to the output stream os.

Returns

A reference to the stream os.

4.2 Function istream& operator>>(istream& is, bitstring& b)

Description

Reads a contiguous sequence of '1's and '0's from a stream into b. Skips whitespace according to is.skipws. The first bit read is bit 0. The first non-whitespace, nonbit character terminates the read and remains in the stream. This function sets is.failbit if the first non-whitespace character is not a '1' or '0'.

Returns

A reference to the stream is.

4.3 Function bitstring operator&(const bitstring& b1, const bitstring& b2)

Description

Computes the bitwise-AND of operands b1 and b2. The length of the result equals the length of the longer operand. This function behaves as if, prior to the operation, the shorter operand was extended with high-order 0 bits until its length equaled that of the longer operand.

Returns

The result of the bitwise-AND in a temporary bitstring.

4.4 Function bitstring operator|(const bitstring& b1, const bitstring& b2)

Description

Computes the bitwise-OR of operands b1 and b2. The length of the result equals the length of the longer operand. This function behaves as if, prior to the operation, the shorter operand was extended with high-order 0 bits until its length equaled that of the longer operand.

Returns

The result of the bitwise-OR in a temporary bitstring.

4.5 Function bitstring operator^(const bitstring& b1, const bitstring& b2)

Des cription

Computes the exclusive-OR of operands b1 and b2. The length of the result equals length of the longer operand. This function behaves as if, prior to the operation, the shorter operand was extended with high-order zero bits until its length equaled that of the longer operand.

Returns

The result of the exclusive-OR in a temporary bitstring.

4.6 Function bitstring operator+(const bitstring& b1, const bitstring& b2)

Description

Appends b non-destructively to *this.

Returns

The result of the concatenation in a temporary bitstring.

Implementation Notes

Following what seems to be the most popular coding style, the class definition  specifies the public interface first, followed by the private artifacts of the implementation. Whenever possible, inline functions appear outside the class definition itself. This placement is not possible in the case of the functions word, offset, mask1, and mask0, because they return objects of the private nested type Block. These functions appeared as macros in last month's C version. As private, static member functions, they are available only to other bitstring member functions.

 Bits of a bitstring are packed in left-to-right into an array of integral blocks. You can substitute any unsigned type for unsigned int in the line

typedef unsigned int Block;

bitstring b1, b2;
// . . .
b1 += b2;
append b2 to b1. Consider the implementation of operator+= from  Listing 5:

bitstring& bitstring::operator+=(const bitstring& b)
{
     assert(nbits_ + b.nbits_ < NPOS);
     int oldlen = nbits_;

     length(nbits_ + b.nbits_);
     for (int i = 0; i < b.nbits_; ++i)
        (void) set_(oldlen+i,b.test_(i));

     return *this;
}
All this function has to do is resize the object and copy b's bits to the end of the object. If the number of bits in b2 is less than or equal to the number of unused bits in b1's allocated memory, then no memory reallocation is necessary. Whether the array needs to be resized or not, the member function length(size_t) resizes the object; if the array must be resized, it handles that too.

The implementation of the global operator+ is now trivial using operator+=:

bitstring operator+(const bitstring& b1, const bitstring&
b2)
{
     bitstring b(b1);
     return b.operator+=(b2);
}
We can do the same sort of thing for all the binary operators. Being small, the binary operators are good candidates for inlining, and therefore appear in bitstring.h.

 

A Simple String Class

The standard C++ string class is a low-level class that contains most of the functionality found in the standard C library's string.h. The string class's key advantages are automatic memory management and the syntactic convenience of overloaded operators. Also, under the current definition, a C++ string may also contain embedded null bytes.

I will not publish the entire string class here. I include only the subset needed to implement the bitstring class . Missing from my listing are functions such as insert, remove, replace and comparison operations. This is a straightforward "cloning" implementation, which makes a separate copy whenever one string is constructed from another (as opposed to a reference-counting scheme which copies only when a string is modified — see Chapter 3 of Jim Coplien's Advanced C++). Implementation  also do not allow embedded nulls in a string.

Listing 1 String Class Definition and Inline Functions

#if !defined(STRING_HPP)
#define STRING_HPP

#include <stddef.h>
#include <assert.h>

class istream;
class ostream;

const size_t NPOS = (size_t) -1;      // "SIZE_T_MAX"

class string
{
public:
    // Constructors / Destructor
    string();
    string(const string& s);
    string(const char *s, size_t n = NPOS);
    ~string();

    // Assignment
    string& operator=(const string& s);

    // Concatenation
    string& operator+=(const string& s);
    friend string operator+(const string& s1, const string& s2);

    // Predicates
    friend int operator==(const string&, const string&);
    friend int operator!=(const string&, const string&);

    // Subscripting
    char get_at(size_t pos) const;
    void put_at(size_t pos, char c);
    string substr(size_t pos, size_t n = NPOS) const;

    // Searching
    size_t find(const string& s, size_t pos = 0) const;
    size_t find_first_of(const string& s, size_t pos = 0) const;
    size_t find_first_not_of(const string& s, size_t pos = 0) const;

    // I/O
    friend ostream& operator<<(ostream&, const string&);
    friend istream& operator>>(istream& is, string&);

    // Miscellaneous
    size_t length() const;
    const char *c_str() const;

private:
    char *data;
    size_t count;   // Doesn't include terminating 0

    void clone(const char *s, size_t n);
};

inline string::string()
{
    *(data = new char[1]) = '\0';
    count = 0;
}

inline string::string(const string& s)
{
    clone(s.data,s.count);
}

inline string::~string()
{
    delete [] data;
}

inline string operator+(const string& s1, const string& s2)
{
    string s(s1);
    return s += s2;
}

inline char string::get_at(size_t pos) const
{
    assert(pos < count);
    return data[pos];
}

inline size_t string::length() const
{
    return count;
}

inline const char * string::c_str() const
{
    return data;
}

#endif

// End of File

Listing 2 String Class Implementation

// string.cpp

#include <iostream.h>
#include <iomanip.h>
#include <string.h>
#include <assert.h>
#include "string.hpp"

string::string(const char *s, size_t n)
{
    assert(s);
    clone(s,n);
}

string& string::operator=(const string& s)
{
    if (this != &s)
    {
        delete [] data;
        clone(s.data,s.count);
    }
    return *this;
}

string& string::operator+=(const string& s)
{
    assert(count + s.count < NPOS);
    if (s.count > 0)
    {
        int new_count = count + s.count;
        char *buf = new char[new_count + 1];

        memcpy(buf,data,count);
        memcpy(buf+count,s.data,s.count);
        buf[new_count] = '\0';
        delete [] data;
        data = buf;
        count = new_count;
    }
    return *this;
}

size_t string::find(const string& s, size_t pos) const
{
    assert(pos < count);
    char *p = strstr(data,s.c_str());
    if (p)
        return pos + (p - data);
    return NPOS;
}

size_t string::find_first_of(const string& s, size_t pos) const
{
    assert(pos < count);
    char *p = strpbrk(data+pos,s.data);
    if (p)
        return p - data;
    return NPOS;
}

size_t string::find_first_not_of(const string& s, size_t pos) const
{
    assert(pos < count);
    for (size_t i = pos; i < count; ++i)
        if (strchr(s.data,data[i]) == NULL)
            return i;
    return NPOS;
}

string string::substr(size_t pos, size_t n) const
{
    assert(pos <= count);
    if (n > count - pos)
        n = count - pos;

    if (n > 0)
        return string(data+pos,n);
    else
        return string();   // Empty string
}
ostream& operator<<(ostream& os, const string& s)
{
    os.write(s.data,s.count);
    return os;
}

istream& operator>>(istream& is, string& s)
{
    const size_t BUFSIZ = 256;
    char buf[BUFSIZ];

    is >> setw(BUFSIZ) >> buf;
    s.clone(buf,strlen(buf));
    return is;
}

void string::put_at(size_t pos, char c)
{
    assert(pos <= count);
    if (pos < count)
        data[pos] = c;
    else
    {
        // Append character the lazy way
        char buf[2];
        baf[0] = c;
        buf[1] = '\0';
        operator+=(buf);
    }
}

int operator==(const string& s1, const string& s2)
{
    return s1.count == s2.count &&
     memcmp(s1.data,s2.data,s1.count) == 0;
}

int operator!=(const string& s1, const string& s2)
{
    return s1.count != s2.count ||
     memcmp(s1.data,s2.data,s1.count);
}

// Private function
void string::clone(const char *s, size_t n)
{
    // Assumes "data" needs to be allocated
    assert(s != NULL);
    size_t len = strlen(s);
    count = (n >= len) ? len : n;
    data = new char[count + 1];
    strncpy(data,s,count)[count] = '\0';
}

// End of File

Listing 3 Illustrates the String Class

// tstr.cpp:     Test the string class

#include <iostream.h>
#include "string.hpp"

main()
{
    size_t pos;
    string s1("Hello"), s2 = ", there, you fool! ", s3;

    // Concatenation
    cout << "s1 + s2 ==" << s1+s2 << endl;
    s1 += s2;
    cout << "Now s1 ==" << s1 << endl;

    // Searching
    if ((pos = s1.find("o")) != NPOS)
        cout << "find(\"o\"): " << pos << endl;
    cout << "s1.substr(4,10) == " << s1.substr(4,10) << endl;
    if ((pos = s1.find_first_of("abcde")) != NPOS)
        cout << "find_first_of(abcde): "<< pos << endl;
    if ((pos = s1.find_first_not_of("abcde")) != NPOS)
        cout << "find_first_not_of(abcde): " << pos << endl;

    // Subscripting
    s3 = "This is a char* constant";
    cout << "s3[0] == " << s3.get_at(0) << endl;
    s3.put_at(3,'3');
    s3.put_at(s3.length(),'!');
    cout << "Now s3 ==" << s3 << endl;

    return 0;
}

/* OUTPUT:
s1 + s2 == Hello, there, you fool!
Now s1 == Hello, there, you fool!
find("o"): 4
s1.substr(4,10) == o, there,
find_first_of(abcde): 1
find_first_not_of(abcde): 0
s3[0] == T
Now s3 == Thi3 is a char* constant!
*/

// End of File

Listing 4 Bitstring Class Definition and Inline Functions

// bitstr.h:  The C++ bitstring class

#if !defined(BITSTR_H)
#define BITSTR_H

#include <stddef.h>
#include <limits.h>
#include <assert.h>
#include "string.hpp"

class istream;
class ostream;

class bitstring
{
public:
    bitstring();
    bitstring(unsigned long, size_t);
    bitstring(const string&);
    bitstring(const bitstring&);
    ~bitstring();

    // Conversions
    string to_string() const;

    // Assignment
    bitstring& operator=(const bitstring&);

    // Equality
    int operator==(const bitstring&) const;
    int operator!=(const bitstring&) const;

    // Basic bit operations
    bitstring& set(size_t, int = 1);
    bitstring& set();
    bitstring& reset(size_t);
    bitstring& reset();
    bitstring& toggle(size_t);
    bitstring& toggle();
    int test(size_t) const;
    int any() const;
    int none() const;
    bitstring operator~() const;
    size_t count() const;

    // Bitwise operators
    bitstring& operator&=(const bitstring&);
    bitstring& operator|(const bitstring&);
    bitstring& operator^=(const bitstring&);
    bitstring& operator>>=(size_t);
    bitstring& operator<<=(size_t);
    bitstring operator>>(size_t) const;
    bitstring operator<<(size_t) const;

    // String operations
    bitstring& operator+=(const bitstring&);
    bitstring& insert(size_t, const bitstring&);
    bitstring& remove(size_t, size_t);
    bitstring& replace(size_t, size_t, const bitstring&);
    size_t find(int, size_t = 0) const;
    size_t rfind(int, size_t = NPOS) const;
    bitstring substr(size_t, size_t) const;
    size_t  length() const;
    size_t length(size_t, int = 0);
    size_t trim();

private:
    typedef unsigned int Block;
    Block *bits_;
    size_t nbits_;
    size_t nblks_;
    Block clean_mask_;

    enum {BLKSIZ = CHAR_BIT * sizeof(Block)};

    static Block word(size_t b)
      {return b / BLKSIZ;}
    static Block offset(size_t b)
      {return BLKSIZ - b%BLKSIZ - 1;}
    static Block mask1(size_t b)
      {return Block(1) << offset(b);}
    static Block mask0(size_t b)
      {return ~(Block(1) << offset(b));}
    static size_t nblks(size_t nb)
      {return (nb+BLKSIZ-1) / BLKSIZ;}

    void make_clean_mask();
    void cleanup();
    void set_(size_t);
    int set_(size_t, int);
    void reset_(size_t);
    int test_(size_t) const;
    void from_string(const string&);
    void init(size_t);
    void equalize(bitstring&);

    friend istream& operator>>(istream&, bitstring&);
};

// Global Functions:
ostream& operator<<(ostream&, const bitstring&);
istream& operator>>(istream&, bitstring&);
bitstring operator& (const bitstring&, const bitstring&);
bitstring operator|(const bitstring&, const bitstring&);
bitstring operator^ (const bitstring&, const bitstring&);
bitstring operator+ (const bitstring&, const bitstring&);

// Inline publics:
inline bitstring::bitstring()
{
    init(0);
}

inline bitstring::~bitstring()
{
    delete [] bits_;
}

inline bitstring& bitstring::toggle(size_t pos)
{
    assert(pos < nbits_);
    bits_[word(pos)] ^= mask1(pos);
    return *this;
}

inline int bitstring::test(size_t pos) const
{
    assert(pos < nbits_);
    return test_(pos);
}

inline bitstring bitstring::operator~() const
{
    bitstring b(*this);
    b.toggle();
    return b;
}

inline int bitstring::operator!=(const bitstring& b) const
{
    return !operator==(b);
}

inline int bitstring::none() const
{
    return !any();
}

inline size_t bitstring::length() const
{
    return nbits_;
}

inline bitstring
operator&(const bitstring& x, const bitstring& y)
{
    bitstring b(x);
    return b &= y;
}

inline bitstring
operator|(const bitstring& x, const bitstring& y)
{
    bitstring b(x);
    return b |= y;
}

inline bitstring
operator^(const bitstring& x, const bitstring& y)
{
    bitstring b(y);
    return b ^= x;
}

inline bitstring bitstring::operator<<(size_t n) const
{
    bitstring r(*this);
    return r <<= n;
}

inline bitstring bitstring::operator>>(size_t n) const
{
    bitstring r(*this);
    return r >>= n;
}

inline bitstring
operator+(const bitstring& b1, const bitstring& b2)
{
    bitstring b(b1);
    return b.operator+=(b2);
}

// Inline privates:
inline void bitstring::make_cleanmask()
{
    clean_mask = ~Block(0) << (nblks_ * BLKSIZ - nbits_);
}

inline void bitstring::cleanup()
{
    // Make sure unused bits don't get set
    if (nbits_ % BLKSIZ)
      bits_[nblks_ - 1] &= clean_mask;
}
inline void bitstring::set_(size_t b)
{
    bits_[word(b)] |= mask1(b);
}

inline void bitstring::reset_(size_t b)
{
    bits_[word(b)] &= mask0(b);
}

inline int bitstring::test_(size_t b) const
{
    return !!(bits_[word(b)] & maskl(b));
}

#endif

// End of File

Listing 5 Bitstring Class Implementation

// bitstr.cpp
#include <assert.h>
#include <iostream.h>
#include <string.h>
#include "string.hpp"
#include "bitstr.h"

// Public Functions:
bitstring::bitstring(unsigned long val, size_t n)
{
    assert(n < NPOS);

    // val == 0 is easy...
    if (val == 0)
    {
        init(n);
        return;
    }

    // Find highest significant bit
    unsigned long temp = val;
    int high_bit = 0;
    for (int i = 0; temp; ++i)
    {
        if (temp & 1)
            high_bit = i;
        temp >>= 1;
    }

    // Determine nbits_ and construct
    nbits_ = high_bit + 1;
    if (nbits_ < n)
        nbits_ = n;
    init(nbits_);

    // Store bit pattern
    for (i = 0; i < nbits_; ++i)
    {
        if (val & 1)
            set_(i);
        val >>= 1;
    }
}
bitstring::bitstring(const string& s)
{
    // Validate that s has only 0's and 1's
    for (int i = 0; i < s.length(); ++i)
    {
        char c = s.get_at(i);
        if (c != '0' && c != '1')
            break;
    }
    assert(i == s.length());

    from_string(s);
}

bitstring::bitstring(const bitstring& b)
{
    init(b.nbits_);
    memcpy(bits_,b.bits_,nblks_ * sizeof bits_[0]);
}

string bitstring::to_string() const
{
    char *s = new char[nbits_+1];
    for (int i = 0; i < nbits_; ++i)
        s[i] = '0' +test_(i);

    s[nbits_] = '\0';
    string s2(s);
    delete [] s;
    return s2;
}

bitstring& bitstring::operator=(const bitstring& b)
{
    if (this != &b)
    {
        delete [] bits_;
        init(b.nbits_);
        memcpy(bits_,b.bits_,nblks_ * sizeof bits_[0]);
    }
    return *this;
}

int bitstring::operator==(const bitstring& b) const
{
    return (nbits_ == b.nbits_) &&
      !memcmp(bits_,b.bits_,nblks_ * sizeof bits_[0]);
}

bitstring& bitstring::set()
{
    memset(bits_,~0u,nblks_* sizeof bits_[0]);
    cleanup();
    return *this;
}

bitstring& bitstring::set(size_t pos, int val)
{
    assert(pos <= nbits_);
    if (pos == nbits_)
       length(nbits_ + 1); // Append

    set_(pos,val);
    return *this;
}

bitstring& bitstring::reset(size_t pos)
{
    assert(pos <= nbits_);
    if (pos == nbits_)
    length(nbits_ + 1); // Append

    reset_(pos);
    return *this;
}

bitstring& bitstring::reset()
{
    memset(bits_,0,nblks_ * sizeof bits_[0]);
    return *this;
}

bitstring& bitstring::toggle()
{
    size_t nw = nblks_;
    while (nw--)
      bits_[nw] = ~bits_[nw];
    cleanup();
    return *this;
}

int bitstring::any() const
{
    for (int i = 0; i < nblks_; ++i)
      if (bits_[i])
        return 1;
    return 0;
}

size_t bitstring::count() const
{
    size_t sum = 0;
    for (int i = 0; i < nbits_; ++i)
      if (test_(i))
        ++sum;
    return sum;
}

bitstring& bitstring::operator&=(const bitstring& b)
{
    bitstring rhs(b);

    equalize(rhs);
    for (int i = 0; i < nblks_;++i)
        bits_[i] &= rhs.bits_[i];
    return *this;
}

bitstring& bitstring::operator|=(const bitstring& b)
{
    bitstring rhs(b);

    equalize(rhs);
    for (int i = 0; i < nblks_; ++i)
        bits_[i] |= rhs.bits_[i];
    cleanup();
    return *this;
}

bitstring& bitstring::operator^=(const bitstring& b)
{
    bitstring rhs(b);

    equalize(rhs);
    for (int i = 0; i < nblks_; ++i)
        bits_[i] ^= rhs.bits_[i];
    cleanup();
    return *this;
}

bitstring& bitstring::operator>>=(size_t n)
{
    if (n >= nbits_)
        reset();
    else
    {
        for (int i = nbits_ - 1; i >= n; --i)
            (void) set_(i,test_(i-n));
        for (i = 0; i < n;++i)
            reset_(i);
    }
    return *this;
}

bitstring& bitstring::operator<<=(size_t n)
{
    if (n >= nbits_)
        reset();
    else
    {
        for (int i = 0; i < nbits_ - n; ++i)
            (void) set_(i,test_(i+n));
        for (i = nbits_ - n; i < nbits_; ++i)
            reset_(i);
    }
    return *this;
}

bitstring& bitstring::operator+=(const bitstring& b)
{
    assert(nbits_ + b.nbits_ < NPOS);
    int oldlen = nbits_;

    length(nbits_ + b.nbits_);
    for (int i = 0; i < b.nbits_; ++i)
        (void) set_(oldlen+i,b.test_(i));

    return *this;
}

bitstring& bitstring::insert(size_t pos, const bitstring& b)
{
    assert(pos <= nbits_);
    size_t oldlen = nbits_;
    size_t newlen = nbits_ + b.nbits_;

    // Grow to accommodate insertion
    length(newlen);

    // Move tail to right
    for (int i = 0; i < oldlen - pos;++i)
        set_(newlen-i-1,test_(oldlen-i-1));

    // Replicate b in between

    for (i = 0; i < b.nbits_;++i)
        set_(pos+i,b.test_(i));

    return *this;
}

bitstring& bitstring::remove(size_t pos, size_t n)
{
    assert(pos < nbits_);

    if (n > nbits_ - pos)
       n = nbits_ - pos;

    // Slide tail down to cover gap
    for (int i = 0; i < nbits_ - pos - n; ++i)
        set_(pos+i,test_(pos+n+i));

    // Shrink
    length(nbits_ - n);
    return *this;
}

bitstring& bitstring::replace(size_t pos, size_t n,
                         const bitstring& b)
{
    assert(pos <= nbits_);
    if (n > nbits_ - pos)
        n = nbits_ - pos;

    size_t oldlen= nbits_;
    size_t newlen = oldlen - n + b.nbits_;
    int diff = newlen - oldlen;

    // Adjust length and move tail as needed
    if (diff > 0)
    {
        // Grow
        length(newlen);
        for (int i = oldlen - 1; i >= pos + n; --i)
        (void) set_(i+diff,test_(i));
    }
    else if (diff < 0)
    {
        // Shrink
        for (int i = pos + n; i < oldlen; ++i)
            (void) set_(i+diff,test_(i));
        length(newlen);
    }

    // Copy b into place
    for (int i = 0; i < b.nbits_;++i)
        (void) set_(pos+i,b.test_(i));

    return *this;
}

size_t bitstring::find(int val, size_t pos) const
{

    assert(pos < nbits_);

    for (size_t i = pos; i < nbits_; ++i)
{
    int t = test_(i);
    if (val && t || !val && !t)
        return i;
}
    return NPOS;
}

size_t bitstring::rfind(int val, size_t pos) const

{
    assert(pos < nbits_);

    for (int i = pos; i >= 0; --i)
    {
        int t = test_(i);
        if (val && t || !val && !t)
            return i;
    }
    return NPOS;
}

bitstring bitstring::substr(size_t pos, size_t n) const
{
    assert(pos <= nbits_);
    if (n > nbits_ - pos)
        n = nbits_ - pos;

    bitstring b(0,n);
    for (int i = 0; i < n; ++i)
        b.set_(i,test_(pos + i));
    return b;
}

size_t bitstring::length(size_t n, int val)
{
    size_t oldlen = nbits_;
    size_t nw1 = nblks_;
    size_t nw2 = nblks(n);

    // Alter the size of a bitstring
    if (nw1 != nw2)
    {
        Block *newbits = new Block[nw2];
        for (int i = 0; i < nwl && i < nw2; ++i)
            newbits[i] = bits_[i];

        delete [] bits_;
        bits_ = newbits;

        for (i = nw1; i < nw2;++i)
            bits_[i] = val ? ~Block(0) : Block(0);
        nblks_ = nw2;
    }
    nbits_ = n;
    make_clean_mask();
    cleanup();
    return oldlen;
}

size_t bitstring::trim()
{
    for (int i = nbits_ - 1; i >= 0; --i)
        if (test_(i))
            break;
    size_t newlen = i + 1;
    length(newlen);
    return newlen;
}

ostream& operator<<(ostream& os, const bitstring& b)
{
    os << b.to_string();
    return os;
}

istream& operator>>(istream& is, bitstring& b)
{
    const size_t N = 256;
    char *buf = new char[N];
    char c;

    // Consume bit characters
    is >> ws;
    for (int i = 0; i < N && is.get(c); ++i)
    {
        if (c == '0' || c == '1')
            buf[i] = c;
        else
        {
            is.putback(c);
            break;
        }
    }
    buf[i] = '\0';

    if (i == 0)
        is.clear(ios::failbit);
    else
    {
        delete [] b.bits_;
        b.from_string(string(buf));
    }

    delete buf;
    return is;
}

// Private Functions:
int bitstring::set_(size_t n, int val)
{
    if (val)
        set_(n);
    else
        reset_(n);
    return !!val;
}

void bitstring::from_string(const string& s)
{
    // Assumes string contains only 0's and 1's
    init(s.length());
    for (int i = 0; i < s.length(); ++i)
        if (s.get_at(i) == '1')
            set_(i);
}

void bitstring::init(size_t n)
{
    nbits_ = n;
    nblks_ = nblks(n);
    if (n > 0)
        bits_ = new Block[nblks_];
    else
        bits_ = 0;
    memset(bits_,0,nblks_ * sizeof bits_[0]);
    make_clean_mask();
}

void bitstring::equalize(bitstring& b)
{
    // Make the smaller the size of the larger
    if (b.nbits_ < nbits_)
        b.length(nbits_);
    else if (nbits_ < b.nbits_)
        length(b.nbits_);
}

// End of File

Listing 6 Illustrates the Bitstring Class

// tbitstr.cpp - Test the bitstring class
#include <iostream.h>
#(include "bitstr.h"
#include "string.hpp"

main()
{
    bitstring x(21537,4), y(string("10110"));

    cout << "Initial x: " << x << endl;
    cout << "Initial y: " << y << endl;

    for (int i = 0; i <= 5; ++i)
        x.set(i);
    cout << "x: "<< x << " (" << x.count()
        <<" bits set)" << endl;
    cout << "x == 21567?"
        << (x == bitstring(21567,x.length())) << endl;
    cout << "x >>= 6 = " << (x >>= 6) << endl;
    cout << "x <<= 6 = " << (x <<= 6) << endl;
    cout << "x ^ 3 = " << (x ^ bitstring(3,2)) << endl;
    cout << "x | 3 = " << (x | bitstring(3,2)) << endl;
    cout << "x & 3 = " << (x & bitstring(3,2)) << endl;
    cout << "3 & x = " << (bitstring(3,2) & x) << endl;
    cout << "~x = " << (~x) << endl;

    cout << "y &= x = " << (y &= x) << endl;
    cout << "y ^= x = " << (y ^= x) << endl;
    cout << "y |= x = " << (y |= x) << endl;

    y.length(20);
    y.reset();
    for (i = 4; i <= 12; ++i)
        y.set(i);
    cout << "y: "<< y << " (" << y.count()
        <<" bits set)" << endl;
    cout << "x & y = " << (x & y) << endl;
    cout << "x | y = " << (x | y) << endl;
    cout << "x ^ y = " << (x ^ y) << endl;
    cout << "x != y? " << (x != y) << endl;
    cout <<"x + y = " << x+y << endl;
    x += y;
    cout << "after x+= y: "<< x << endl;
    x.trim();
    cout << "after x.trim(): "<< x << endl;

    return 0;
}

/* OUTPUT:
Initial x: 100001000010101
Initial y: 10110
x: 111111000010101 (9 bits set)
x == 21567? 1
x >>= 6 = 000000111111000
x <<= 6 = 111111000000000
x ^ 3 = 001111000000000
x | 3 = 111111000000000
x & 3 = 110000000000000
3 & x = 110000000000000
~x = 000000111111111
y &= x = 101100000000000
y ^= x = 010011000000000
y |= x = 111111000000000
y: 00001111111110000000 (9 bits set)
x & y = 00001100000000000000
x | y = 11111111111110000000
x ^ y = 11110011111110000000
x != y? 1
x + y = 11111100000000000001111111110000000
after x += y: 11111100000000000001111111110000000
after x.trim(): 1111110000000000000111111111
*/