Chapter 9: More Operator Overloading

Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.

Please state the document version you're referring to, as found in the title (in this document: 7.2.0) and please state chapter and paragraph name or number you're referring to.

All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.

Having covered the overloaded assignment operator in chapter 7, and having shown several examples of other overloaded operators as well (i.e., the insertion and extraction operators in chapters 3 and 5), we will now take a look at several other interesting examples of operator overloading.

9.1: Overloading `operator[]()'

As our next example of operator overloading, we present a class operating on an array of ints. Indexing the array elements occurs with the standard array operator [], but additionally the class checks for boundary overflow. Furthermore, the index operator ( operator[]()) is interesting in that it both produces a value and accepts a value, when used, respectively, as a right-hand value ( rvalue) and a left-hand value ( lvalue) in expressions. Here is an example showing the use of the class:
    int main()
    {
        IntArray x(20);                 // 20 ints

        for (int i = 0; i < 20; i++)
            x[i] = i * 2;               // assign the elements

        for (int i = 0; i <= 20; i++)   // produces boundary overflow
            cout << "At index " << i << ": value is " << x[i] << endl;
    }
First, the constructor is used to create an object containing 20 ints. The elements stored in the object can be assigned or retrieved: the first for-loop assigns values to the elements using the index operator, the second for-loop retrieves the values, but will also produce a run-time error as the non-existing value x[20] is addressed. The IntArray class interface is:
    class IntArray
    {
        int     *d_data;
        unsigned d_size;

         public:
            IntArray(unsigned size = 1);
            IntArray(IntArray const &other);
            ~IntArray();
            IntArray const &operator=(IntArray const &other);

                                                // overloaded index operators:
            int &operator[](unsigned index);                // first
            int const &operator[](unsigned index) const;    // second
        private:
            void boundary(unsigned index) const;
            void copy(IntArray const &other);
            int &operatorIndex(unsigned index) const;
    };
This class has the following characteristics: Now, the implementation of the members are:
    #include "intarray.ih"

    IntArray::IntArray(unsigned size)
    :
        d_size(size)
    {
        if (d_size < 1)
        {
            cerr << "IntArray: size of array must be >= 1\n";
            exit(1);
        }
        d_data = new int[d_size];
    }

    IntArray::IntArray(IntArray const &other)
    {
        copy(other);
    }

    IntArray::~IntArray()
    {
        delete[] d_data;
    }

    IntArray const &IntArray::operator=(IntArray const &other)
    {
        if (this != &other)
        {
            delete[] d_data;
            copy(other);
        }
        return *this;
    }

    void IntArray::copy(IntArray const &other)
    {
        d_size = other.d_size;
        d_data = new int[d_size];
        memcpy(d_data, other.d_data, d_size * sizeof(int));
    }

    int &IntArray::operatorIndex(unsigned index) const
    {
        boundary(index);
        return d_data[index];
    }

    int &IntArray::operator[](unsigned index)
    {
        return operatorIndex(index);
    }

    int const &IntArray::operator[](unsigned index) const
    {
        return operatorIndex(index);
    }

    void IntArray::boundary(unsigned index) const
    {
        if (index >= d_size)
        {
            cerr << "IntArray: boundary overflow, index = " <<
                    index << ", should range from 0 to " << d_size - 1 << endl;
            exit(1);
        }
    }
Especially note the implementation of the operator[]() functions: as non-const members may call const member functions, and as the implementation of the const member function is identical to the non-const member function's implementation, we could implement both operator[] members inline using an auxiliary function int &operatorIndex(size_t index) const. It is interesting to note that a const member function may return a non-const reference (or pointer) return value, referring to one of the data members of its object. This is a potentially dangerous backdoor breaking data hiding. However, as the members in the public interface prevents this breach, we feel confident in defining int &operatorIndex() const as a private function, knowing that it won't be used for this unwanted purpose.

9.2: Overloading the insertion and extraction operators

This section describes how a class can be adapted in such a way that it can be used with the C++ streams cout and cerr and the insertion operator (<<). Adapting a class in such a way that the istream's extraction operator (>>) can be used, is implemented similarly and is simply shown in an example.

The implementation of an overloaded operator<<() in the context of cout or cerr involves their class, which is ostream. This class is declared in the header file ostream and defines only overloaded operator functions for `basic' types, such as, int, char *, etc.. The purpose of this section is to show how an insertion operator can be overloaded in such a way that an object of any class, say Person (see chapter 7), can be inserted into an ostream. Having made available such an overloaded operator, the following will be possible:

    Person kr("Kernighan and Ritchie", "unknown", "unknown");

    cout << "Name, address and phone number of Person kr:\n" << kr << endl;
The statement cout << kr involves operator<<(). This member function has two operands: an ostream & and a Person &. The proposed action is defined in an overloaded global operator operator<<() expecting two arguments:
                                // assume declared in `person.h'
    ostream &operator<<(ostream &, Person const &);

                                // define in some source file
    ostream &operator<<(ostream &stream, Person const &pers)
    {
        return
            stream <<
                "Name:    " << pers.name() <<
                "Address: " << pers.address() <<
                "Phone:   " << pers.phone();
    }
Note the following characteristics of operator<<():

In order to overload the extraction operator for, e.g., the Person class, members are needed to modify the private data members. Such modifiers are normally included in the class interface. For the Person class, the following members should be added to the class interface:

    void setName(char const *name);
    void setAddress(char const *address);
    void setPhone(char const *phone);
The implementation of these members could be straightforward: the memory pointed to by the corresponding data member must be deleted, and the data member should point to a copy of the text pointed to by the parameter. E.g.,
    void Person::setAddress(char const *address)
    {
        delete d_address;
        d_address = strdupnew(address);
    }
A more elaborate function could also check the reasonableness of the new address. This elaboration, however, is not further pursued here. Instead, let's have a look at the final overloaded extraction operator (>>). A simple implementation is:
    istream &operator>>(istream &str, Person &p)
    {
        string name;
        string address;
        string phone;

        if (str >> name >> address >> phone)    // extract three strings
        {
            p.setName(name.c_str());
            p.setAddress(address.c_str());
            p.setPhon(phone.c_str());
        }
        return str;
    }
Note the stepwise approach that is followed with the extraction operator: first the required information is extracted using available extraction operators (like a string-extraction), then, if that succeeds, modifier members are used to modify the data members of the object to be extracted. Finally, the stream object itself is returned as a reference.

9.3: Conversion operators

A class may be constructed around a basic type. E.g., the class String was constructed around the char * type. Such a class may define all kinds of operations, like assignments. Take a look at the following class interface, designed after the string class:
    class String
    {
        char *d_string;

        public:
            String();
            String(char const *arg);
            ~String();
            String(String const &other);
            String const &operator=(String const &rvalue);
            String const &operator=(char const *rvalue);
    };
Objects from this class can be initialized from a char const *, and also from a String itself. There is an overloaded assignment operator, allowing the assignment from a String object and from a char const * (Note that the assignment from a char const * also includes the null-pointer. An assignment like stringObject = 0 is perfectly in order.).

Usually, in classes that are less directly coupled to their data than this String class, there will be an accessor member function, like char const *String::c_str() const. However, the need to use this latter member doesn't appeal to our intuition when an array of String objects is defined by, e.g., a class StringArray. If this latter class provides the operator[] to access individual String members, we would have the following interface for StringArray:

    class StringArray
    {
        String *d_store;
        size_t d_n;

        public:
            StringArray(size_t size);
            StringArray(StringArray const &other);
            StringArray const &operator=(StringArray const &rvalue);
            ~StringArray();

            String &operator[](size_t index);
    };
Using the StringArray::operator[], assignments between the String elements can simply be implemented:
    StringArray sa(10);

    sa[4] = sa[3];  // String to String assignment
It is also possible to assign a char const * to an element of sa:
        sa[3] = "hello world";
Here, the following steps are taken: Now we try to do it the other way around: how to access the char const * that's stored in sa[3]? We try the following code:
    char const
        *cp = sa[3];
This, however, won't work: we would need an overloaded assignment operator for the 'class char const *'. Unfortunately, there isn't such a class, and therefore we can't build that overloaded assignment operator (see also section 9.11). Furthermore, casting won't work: the compiler doesn't know how to cast a String to a char const *. How to proceed from here?

The naive solution is to resort to the accessor member function c_str():

        cp = sa[3].c_str()
That solution would work, but it looks so clumsy.... A far better approach would be to use a conversion operator.

A conversion operator is a kind of overloaded operator, but this time the overloading is used to cast the object to another type. Using a conversion operator a String object may be interpreted as a char const *, which can then be assigned to another char const *. Conversion operators can be implemented for all types for which a conversion is needed.

In the current example, the class String would need a conversion operator for a char const *. In class interfaces, the general form of a conversion operator is:

        operator <type>();
In our String class, this would become:
        operator char const *();
The implementation of the conversion operator is straightforward:
    String::operator char const *()
    {
        return d_string;
    }
Notes:

One might wonder what will happen if an object for which, e.g., a string conversion operator is defined is inserted into, e.g., an ostream object, into which string objects can be inserted. In this case, the compiler will not look for appropriate conversion operators (like operator string()), but will report an error. For example, the following example produces a compilation error:

    #include <iostream>
    #include <string>
    using namespace std;

    class NoInsertion
    {
        public:
            operator string() const;
    };

    int main()
    {
        NoInsertion object;

        cout << object << endl;
    }
The problem is caused by the fact that the compiler notices an insertion, applied to an object. It will now look for an appropriate overloaded version of the insertion operator. As it can't find one, it reports a compilation error, instead of performing a two-stage insertion: first using the operator string() insertion, followed by the insertion of that string into the ostream object.

Conversion operators are used when the compiler is given no choice: an assignment of a NoInsertion object to a string object is such a situation. The problem of how to insert an object into, e.g., an ostream is simply solved: by defining an appropriate overloaded insertion operator, rather than by resorting to a conversion operator.

Several considerations apply to conversion operators:

9.4: The keyword `explicit'

Conversions are performed not only by conversion operators, but also by constructors having one parameter (or multiple parameters, having default argument values beyond the first parameter).

Consider the class Person introduced in chapter 7. This class has a constructor

        Person(char const *name, char const *address, char const *phone)
This constructor could be given default argument values:
    Person(char const *name, char const *address = "<unknown>",
                             char const *phone = "<unknown>");
In several situations this constructor might be used intentionally, possibly providing the default <unknown> texts for the address and phone numbers. For example:
    Person frank("Frank", "Room 113", "050 363 9281");
Also, functions might use Person objects as parameters, e.g., the following member in a fictitious class PersonData could be available:
    PersonData &PersonData::operator+=(Person const &person);
Now, combining the above two pieces of code, we might, do something like
    PersonData dbase;

    dbase += frank;     // add frank to the database
So far, so good. However, since the Person constructor can also be used as a conversion operator, it is also possible to do:
    dbase += "karel";
Here, the char const * text `karel' is converted to an (anonymous) Person object using the abovementioned Person constructor: the second and third parameters use their default values. Here, an implicit conversion is performed from a char const * to a Person object, which might not be what the programmer had in mind when the class Person was constructed.

As another example, consider the situation where a class representing a container is constructed. Let's assume that the initial construction of objects of this class is rather complex and time-consuming, but expanding an object so that it can accomodate more elements is even more time-consuming. Such a situation might arise when a hash-table is initially constructed to contain n elements: that's OK as long as the table is not full, but when the table must be expanded, all its elements normally must be rehashed to allow for the new table size.

Such a class could (partially) be defined as follows:

    class HashTable
    {
        size_t d_maxsize;

        public:
            HashTable(size_t n);  // n: initial table size
            size_t size();        // returns current # of elements

                                    // add new key and value
            void add(std::string const &key, std::string const &value);
    };
Now consider the following implementation of add():
    void HashTable::add(string const &key, string const &value)
    {
        if (size() > d_maxsize * 0.75)  // table gets rather full
            *this = size() * 2;         // Oops: not what we want!

        // etc.
    }
In the first line of the body of add() the programmer first determines how full the hashtable currently is: if it's more than three quarter full, then the intention is to double the size of the hashtable. Although this succeeds, the hashtable will completely fail to fulfill its purpose: accidentally the programmer assigns an size_t value, intending to tell the hashtable what its new size should be. This results in the following unwelcome surprise: If an implicit use of a constructor is not appropriate (or dangerous), it can be prevented using the explicit modifier with the constructor. Constructors using the explicit modifier can only be used for the explicit construction of objects, and cannot be used as implicit type convertors anymore. For example, to prevent the implicit conversion from size_t to HashTable the class interface of the class HashTable should declare the constructor
    explicit HashTable(size_t n);
Now the compiler will catch the error in the compilation of HashTable::add(), producing an error message like
    error: no match for 'operator=' in
                '*this = (this->HashTable::size()() * 2)'

9.5: Overloading the increment and decrement operators

Overloading the increment operator ( operator++()) and decrement operator ( operator--()) creates a little problem: there are two version of each operator, as they may be used as postfix operator (e.g., x++) or as prefix operator (e.g., ++x).

Used as postfix operator, the value's object is returned as rvalue, which is an expression having a fixed value: the post-incremented variable itself disappears from view. Used as prefix operator, the variable is incremented, and its value is returned as lvalue, so it can be altered immediately again. Whereas these characteristics are not required when the operator is overloaded, it is strongly advised to implement these characteristics in any overloaded increment or decrement operator.

Suppose we define a wrapper class around the size_t value type. The class could have the following (partially shown) interface:

    class Unsigned
    {
        size_t d_value;

        public:
            Unsigned();
            Unsigned(size_t init);
            Unsigned &operator++();
    }
This defines the prefix overloaded increment operator. An lvalue is returned, as we can deduce from the return type, which is Unsigned &.

The implementation of the above function could be:

    Unsigned &Unsigned::operator++()
    {
        ++d_value;
        return *this;
    }
In order to define the postfix operator, an overloaded version of the operator is defined, expecting an int argument. This might be considered a kludge, or an acceptable application of function overloading. Whatever your opinion in this matter, the following can be concluded: To add the postfix increment operator to the Unsigned wrapper class, add the following line to the class interface:
    Unsigned const operator++(int);
The implementation of the postfix increment operator should be like this:
    Unsigned const Unsigned::operator++(int)
    {
        return d_value++;
    }
The simplicity of this implementation is deceiving. Note that: When the object has a more complex data organization using a copy constructor might be preferred. For instance, assume we want to implement the postfix increment operator in the class PersonData, mentioned in section 9.4. Presumably, the PersonData class contains a complex inner data organization. If the PersonData class would maintain a pointer Person *current to the Person object that is currently selected, then the postfix increment operator for the class PersonData could be implemented as follows:
    PersonData PersonData::operator++(int)
    {
        PersonData tmp(*this);

        incrementCurrent();     // increment `current', somehow.
        return tmp;
    }
A matter of concern here could be that this operation actually requires two calls to the copy constructor: first to keep the current state, then to copy the tmp object to the (anonymous) return value. In some cases this double call of the copy constructor might be avoidable, by defining a specialized constructor. E.g.,
    PersonData PersonData::operator++(int)
    {
        return PersonData(*this, incrementCurrent());
    }
Here, incrementCurrent() is supposed to return the information which allows the constructor to set its current data member to the pre-increment value, at the same time incrementing current of the actual PersonData object. The above constructor would have to: At the same time, incrementCurrent() would have incremented current of the actual PersonData object.

The general rule is that double calls of the copy constructor can be avoided if a specialized constructor can be defined initializing an object to the pre-increment state of the current object. The current object itself has its necessary data members incremented by a function, whose return value is passed as argument to the constructor, thereby informing the constructor of the pre-incremented state of the involved data members. The postfix increment operator will then return the thus constructed (anonymous) object, and no copy constructor is ever called.

Finally it is noted that the call of the increment or decrement operator using its overloaded function name might require us to provide an (any) int argument to inform the compiler that we want the postfix increment function. E.g.,

    PersonData p;

    p = other.operator++();     // incrementing `other', then assigning `p'
    p = other.operator++(0);    // assigning `p', then incrementing `other'

9.6: Overloading binary operators

In various classes overloading binary operators (like operator+()) can be a very natural extension of the class's functionality. For example, the std::string class has various overloaded forms of operator+() as have most abstract containers, covered in chapter 12.

Most binary operators come in two flavors: the plain binary operator (like the + operator) and the arithmetic assignment variant (like the += operator). Whereas the plain binary operators return const expression values, the arithmetic assignment operators return a (non-const) reference to the object to which the operator was applied. For example, with std::string objects the following code (annotated below the example) may be used:

    std::string s1;
    std::string s2;
    std::string s3;

    s1 = s2 += s3;                  // 1
    (s2 += s3) + " postfix";        // 2
    s1 = "prefix " + s3;            // 3
    "prefix " + s3 + "postfix";     // 4
    ("prefix " + s3) += "postfix";  // 5

Now consider the following code, in which a class Binary supports an overloaded operator+():

    class Binary
    {
        public:
            Binary();
            Binary(int value);
            Binary const operator+(Binary const &rvalue);
    };

    int main()
    {
        Binary b1;
        Binary b2(5);

        b1 = b2 + 3;            // 1
        b1 = 3 + b2;            // 2
    }
Compilation of this little program fails for statement // 2, with the compiler reporting an error like:
    error: no match for 'operator+' in '3 + b2'
Why is statement // 1 compiled correctly whereas statement // 2 won't compile?

In order to understand this, the notion of a promotion is introduced. As we have seen in section 9.4, constructors requiring a single argument may be implicitly activated when an object is apparently initialized by an argument of a corresponding type. We've encountered this repeatedly with std::string objects, when an ASCII-Z string was used to initialize a std::string object.

In situations where a member function expects a const & to an object of its own class (like the Binary const & that was specified in the declaration of the Binary::operator+() member mentioned above), the type of the actually used argument may also be any type that can be used as an argument for a single-argument constructor of that class. This implicit call of a constructor to obtain an object of the proper type is called a promotion.

So, in statement // 1, the + operator is called for the b2 object. This operator expects another Binary object as its right hand operand. However, an int is provided. As a constructor Binary(int) exists, the int value is first promoted to a Binary object. Next, this Binary object is passed as argument to the operator+() member.

Note that no promotions are possibly in statement // 2: here the + operator is applied to an int typed value, which has no concept of a `constructor', `member function' or `promotion'.

How, then, are promotions of left-hand operands implemented in statements like "prefix " + s3? Since promotions are applied to function arguments, we must make sure that both operands of binary operators are arguments. This means that binary operators are declared as classless functions, also called free functions. However, they conceptually belong to the class for which they implement the binary operator, and so they should be declared in the class's header file. We will cover their implementations shortly, but here is our first revision of the declaration of the class Binary, declaring an overloaded + operator as a free function:

    class Binary
    {
        public:
            Binary();
            Binary(int value);
    };

    Binary const operator+(Binary const &l_hand, Binary const &r_hand);

By defining binary operators as free functions, the following promotions are possible:

The next step is to implement the corresponding overloaded arithmetic assignment operator. As this operator always has a left-hand operand which is an object of its own class, it is implemented as a true member function. Furthermore, the arithmetic assignment operator should return a reference to the object to which the arithmetic operation applies, as the object might be modified in the same statement. E.g., (s2 += s3) + " postfix". Here is our second revision of the class Binary, showing both the declaration of the plain binary operator and the corresponding arithmetic assignment operator:

    class Binary
    {
        public:
            Binary();
            Binary(int value);
            Binary const operator+(Binary const &rvalue);

            Binary &operator+=(Binary const &other);
    };

    Binary const operator+(Binary const &l_hand, Binary const &r_hand);

Finally, having available the arithmetic assignment operator, the implementation of the plain binary operator turns out to be extremely simple. It contains of a single return statement, in which an anonymous object is constructed to which the arithmetic assignment operator is applied. This anonymous object is then returned by the plain binary operator as its const return value. Since its implementation consists of merely one statement it is usually provided in-line, adding to its efficiency:

    class Binary
    {
        public:
            Binary();
            Binary(int value);
            Binary const operator+(Binary const &rvalue);

            Binary &operator+=(Binary const &other);
    };

    Binary const operator+(Binary const &l_hand, Binary const &r_hand)
    {
        return Binary(l_hand) += r_hand;
    }
One might wonder where the temporary value is located. Most compilers apply in these cases a procedure called ` return value optimization': the anonymous object is created at the location where the eventual returned object will be stored. So, rather than first creating a separate temporary object, and then copying this object later on to the return value, it initializes the return value using the l_hand argument, and then applies the += operator to add the r_hand argument to it. Without return value optimization it would have to: Return value optimization is not required, but optionally available to compilers. As it has no negative side effects, most compiler use it.

9.7: Overloading `operator new(size_t)'

When operator new is overloaded, it must have a void * return type, and at least an argument of type size_t. The size_t type is defined in the header file cstddef, which must therefore be included when the operator new is overloaded.

It is also possible to define multiple versions of the operator new, as long as each version has its own unique set of arguments. The global new operator can still be used, through the ::-operator. If a class X overloads the operator new, then the system-provided operator new is activated by

        X *x = ::new X();
Overloading new[] is discussed in section 9.9. The following example shows an overloaded version of operator new:
    #include <cstddef>

    void *X::operator new(size_t sizeofX)
    {
        void *p = new char[sizeofX];

        return memset(p, 0, sizeof(X));
    }
Now, let's see what happens when operator new is overloaded for the class X. Assume that class is defined as follows (For the sake of simplicity we have violated the principle of encapsulation here. The principle of encapsulation, however, is immaterial to the discussion of the workings of the operator new.):
    class X
    {
        public:
            void *operator new(size_t sizeofX);

            int d_x;
            int d_y;
    };
Now, consider the following program fragment:
    #include "x.h"  // class X interface
    #include <iostream>
    using namespace std;

    int main()
    {
        X *x = new X();

        cout << x->d_x << ", " << x->d_y << endl;
    }
This small program produces the following output:
        0, 0
At the call of new X(), our little program performed the following actions: Due to the initialization of the block of memory by operator new the allocated X object was already initialized to zeros when the constructor was called.

Non-static member functions are passed a (hidden) pointer to the object on which they should operate. This hidden pointer becomes the this pointer in non-static member functions. This procedure is also followed for constructors. In the next pieces of pseudo C++ code, the pointer is made visible. In the first part an X object x is defined directly, in the second part of the example the (overloaded) operator new is used:

    X::X(&x);                       // x's address is passed to the
                                    // constructor
    void *ptr = X::operator new();  // new allocates the memory

    X::X(ptr);                      // next the constructor operates on the
                                    // memory returned by 'operator new'
Notice that in the pseudo C++ fragment the member functions were treated as static member function of the class X. Actually, operator new is a static member function of its class: it cannot reach data members of its object, since it's normally the task of the operator new to create room for that object. It can do that by allocating enough memory, and by initializing the area as required. Next, the memory is passed (as the this pointer) to the constructor for further processing. The fact that an overloaded operator new is actually a static function, not requiring an object of its class, can be illustrated in the following (frowned upon in normal situations!) program fragment, which can be compiled without problems (assume class X has been defined and is available as before):
    int main()
    {
        X x;

        X::operator new(sizeof x);
    }
The call to X::operator new() returns a void * to an initialized block of memory, the size of an X object.

The operator new can have multiple parameters. The first parameter is initialized by an implicit argument and is always the size_t parameter, other parameters are initialized by explicit arguments that are specified when operator new is used. For example:

    class X
    {
        public:
            void *operator new(size_t p1, size_t p2);
            void *operator new(size_t p1, char const *fmt, ...);
    };

    int main()
    {
        X
            *p1 = new(12) X(),
            *p2 = new("%d %d", 12, 13) X(),
            *p3 = new("%d", 12) X();
    }
The pointer p1 is a pointer to an X object for which the memory has been allocated by the call to the first overloaded operator new, followed by the call of the constructor X() for that block of memory. The pointer p2 is a pointer to an X object for which the memory has been allocated by the call to the second overloaded operator new, followed again by a call of the constructor X() for its block of memory. Notice that pointer p3 also uses the second overloaded operator new(), as that overloaded operator accepts a variable number of arguments, the first of which is a char const *.

Finally note that no explicit argument is passed for new's first parameter, as this argument is implicitly provided by the type specification that's required for operator new.

9.8: Overloading `operator delete(void *)'

The delete operator may be overloaded too. The operator delete must have a void * argument, and an optional second argument of type size_t, which is the size in bytes of objects of the class for which the operator delete is overloaded. The return type of the overloaded operator delete is void.

Therefore, in a class the operator delete may be overloaded using the following prototype:

        void operator delete(void *);
or
        void operator delete(void *, size_t);
Overloading delete[] is discussed in section 9.9.

The `home-made' operator delete is called after executing the destructor of the associated class. So, the statement

        delete ptr;
with ptr being a pointer to an object of the class X for which the operator delete was overloaded, boils down to the following statements:
    X::~X(ptr);     // call the destructor function itself

                    // and do things with the memory pointed to by ptr
    X::operator delete(ptr, sizeof(*ptr));
The overloaded operator delete may do whatever it wants to do with the memory pointed to by ptr. It could, e.g., simply delete it. If that would be the preferred thing to do, then the default delete operator can be activated using the :: scope resolution operator. For example:
    void X::operator delete(void *ptr)
    {
        // any operation considered necessary, then:
        ::delete ptr;
    }

9.9: Operators `new[]' and `delete[]'

In sections 7.1.1, 7.1.2 and 7.2.1 operator new[] and operator delete[] were introduced. Like operator new and operator delete the operators new[] and delete[] may be overloaded. Because it is possible to overload new[] and delete[] as well as operator new and operator delete, one should be careful in selecting the appropriate set of operators. The following rule of thumb should be followed:
If new is used to allocate memory, delete should be used to deallocate memory. If new[] is used to allocate memory, delete[] should be used to deallocate memory.

The default way these operators act is as follows:

Operators new[] and delete[] may only be overloaded in classes. Consequently, when allocating primitive types or pointers to objects only the default line of action is followed: when arrays of pointers to objects are deleted, a memory leak occurs unless the objects to which the pointers point were deleted earlier.

In this section the mere syntax for overloading operators new[] and delete[] is presented. It is left as an exercise to the reader to make good use of these overloaded operators.

9.9.1: Overloading `new[]'

To overload operator new[] in a class Object the interface should contain the following lines, showing multiple forms of overloaded forms of operator new[]:
    class Object
    {
        public:
            void *operator new[](size_t size);
            void *operator new[](size_t index, size_t extra);
    };
The first form shows the basic form of operator new[]. It should return a void *, and defines at least a size_t parameter. When operator new[] is called, size contains the number of bytes that must be allocated for the required number of objects. These objects can be initialized by the global operator new[] using the form
    ::new Object[size / sizeof(Object)]
Or, alternatively, the required (uninitialized) amount of memory can be allocated using:
    ::new char[size]
An example of an overloaded operator new[] member function, returning an array of Object objects all filled with 0-bytes, is:
    void *Object::operator new[](size_t size)
    {
        return memset(new char[size], 0, size);
    }
Having constructed the overloaded operator new[], it will be used automatically in statements like:
    Object *op = new Object[12];
Operator new[] may be overloaded using additional parameters. The second form of the overloaded operator new[] shows such an additional size_t parameter. The definition of such a function is standard, and could be:
    void *Object::operator new[](size_t size, size_t extra)
    {
        size_t n = size / sizeof(Object);
        Object *op = ::new Object[n];

        for (size_t idx = 0; idx < n; idx++)
            op[idx].value = extra;          // assume a member `value'

        return op;
    }
To use this overloaded operator, only the additional parameter must be provided. It is given in a parameter list just after the name of the operator itself:
    Object
        *op = new(100) Object[12];
This results in an array of 12 Object objects, all having their value members set to 100.

9.9.2: Overloading `delete[]'

Like operator new[] operator delete[] may be overloaded. To overload operator delete[] in a class Object the interface should contain the following lines, showing multiple forms of overloaded forms of operator delete[]:
    class Object
    {
        public:
            void operator delete[](void *p);
            void operator delete[](void *p, size_t index);
            void operator delete[](void *p, int extra, bool yes);
    };

9.9.2.1: `delete[](void *)'

The first form shows the basic form of operator delete[]. Its parameter is initialized to the address of a block of memory previously allocated by Object::new[]. These objects can be deleted by the global operator delete[] using the form ::delete[]. However, the compiler expects ::delete[] to receive a pointer to Objects, so a type cast is necessary:

    ::delete[] reinterpret_cast<Object *>(p);
An example of an overloaded operator delete[] is:
    void Object::operator delete[](void *p)
    {
        cout << "operator delete[] for Objects called\n";
        ::delete[] reinterpret_cast<Object *>(p);
    }
Having constructed the overloaded operator delete[], it will be used automatically in statements like:
        delete[] new Object[5];

9.9.2.2: `delete[](void *, size_t)'

Operator delete[] may be overloaded using additional parameters. However, if overloaded as

    void operator delete[](void *p, size_t size);
then size is automatically initialized to the size (in bytes) of the block of memory to which void *p points. If this form is defined, then the first form should not be defined, to avoid ambiguity. An example of this form of operator delete[] is:
    void Object::operator delete[](void *p, size_t size)
    {
        cout << "deleting " << size << " bytes\n";
        ::delete[] reinterpret_cast<Object *>(p);
    }

9.9.2.3: Alternate forms of overloading operator `delete[]'

If additional parameters are defined, as in

    void operator delete[](void *p, int extra, bool yes);
an explicit argument list must be provided. With delete[], the argument list is specified following the brackets:
    delete[](new Object[5], 100, false);

9.10: Function Objects

Function Objects are created by overloading the function call operator operator()(). By defining the function call operator an object masquerades as a function, hence the term function objects.

Function objects play an important role in generic algorithms and their use is preferred over alternatives like pointers to functions. The fact that they are important in the context of generic algorithms leaves us in a didactic dilemma: at this point it would have been nice if generic algorithms would have been covered, but for the discussion of the generic algorithms knowledge of function objects is required. This bootstrapping problem is solved in a well known way: by ignoring the dependency for the time being.

Function objects are objects for which operator()() has been defined. Function objects are commonly used in combination with generic algorithms, but also in situations where otherwise pointers to functions would have been used. Another reason for using function objects is to support inline functions, which cannot be used in combination with pointers to functions.

An important set of functions and function objects is the set of predicate functions and function objects. The return value of a predicate function or of the function call operator of a predicate function object is true or false. Both predicate functions and predicate function objects are commonly referred to as `predicates'. Predicates are frequently used by generic algorithms. E.g., the count_if generic algorithm, covered in chapter 17, returns the number of times the function object that's passed to it returns true. In the standard template library two kinds of predicates are used: unary predicates receive one argument, binary predicates receive two arguments.

Assume we have a class Person and an array of Person objects. Further assume that the array is not sorted. A well known procedure for finding a particular Person object in the array is to use the function lsearch(), which performs a lineair search in an array. A program fragment using this function is:

    Person &target = targetPerson();    // determine the person to find
    Person *pArray;
    size_t n = fillPerson(&pArray);

    cout << "The target person is";

    if (!lsearch(&target, pArray, &n, sizeof(Person), compareFunction))
        cout << " not";

    cout << "found\n";
The function targetPerson() is called to determine the person we're looking for, and the function fillPerson() is called to fill the array. Then lsearch() is used to locate the target person.

The comparison function must be available, as its address is one of the arguments of the lsearch() function. It could be something like:

    int compareFunction(Person const *p1, Person const *p2)
    {
        return *p1 != *p2;      // lsearch() wants 0 for equal objects
    }
This, of course, assumes that the operator!=() has been overloaded in the class Person, as it is quite unlikely that a bytewise comparison will be appropriate here. But overloading operator!=() is no big deal, so let's assume that that operator is available as well.

With lsearch() (and friends, having parameters that are pointers to functions) an inline compare function cannot be used: as the address of the compare() function must be known to the lsearch() function. So, on average n / 2 times at least the following actions take place:

  1. The two arguments of the compare function are pushed on the stack;
  2. The value of the final parameter of lsearch() is determined, producing the address of compareFunction();
  3. The compare function is called;
  4. Then, inside the compare function the address of the right-hand argument of the Person::operator!=() argument is pushed on the stack;
  5. The Person::operator!=() function is evaluated;
  6. The argument of the Person::operator!=() function is popped off the stack again;
  7. The two arguments of the compare function are popped off the stack again.
When function objects are used a different picture emerges. Assume we have constructed a function PersonSearch(), having the following prototype (this, however, is not the preferred approach. Normally a generic algorithm will be preferred to a home-made function. But for now our PersonSearch() function is used to illustrate the use and implementation of a function object):
    Person const *PersonSearch(Person *base, size_t nmemb,
                               Person const &target);
This function can be used as follows:
    Person &target = targetPerson();
    Person *pArray;
    size_t n = fillPerson(&pArray);

    cout << "The target person is";

    if (!PersonSearch(pArray, n, target))
        cout << " not";

    cout << "found\n";
So far, nothing much has been altered. We've replaced the call to lsearch() with a call to another function: PersonSearch(). Now we show what happens inside PersonSearch():
    Person const *PersonSearch(Person *base, size_t nmemb,
                                Person const &target)
    {
        for (int idx = 0; idx < nmemb; ++idx)
            if (target(base[idx]))
                return base + idx;
        return 0;
    }
The implementation shows a plain linear search. However, in the for-loop the expression target(base[idx]) shows our target object used as a function object. Its implementation can be simple:
    bool Person::operator()(Person const &other) const
    {
        return *this != other;
    }
Note the somewhat peculiar syntax: operator()(). The first set of parentheses define the particular operator that is overloaded: the function call operator. The second set of parentheses define the parameters that are required for this function. Operator()() appears in the class header file as:
    bool operator()(Person const &other) const;
Now, Person::operator()() is a simple function. It contains but one statement, so we could consider making it inline. Assuming that we do, than this is what happens when operator()() is called: Note that due to the fact that operator()() is an inline function, it is not actually called. Instead operator!=() is called immediately. Also note that the required stack operations are fairly modest.

So, function objects may be defined inline. This is not possible for functions that are called indirectly (i.e., using pointers to functions). Therefore, even if the function object needs to do very little work it has to be defined as an ordinary function if it is going to be called via pointers. The overhead of performing the indirect call may annihilate the advantage of the flexibility of calling functions indirectly. In these cases function objects that are defined as inline functions can result in an increase of efficiency of the program.

Finally, function objects may access the private data of their objects directly. In a search algorithm where a compare function is used (as with lsearch()) the target and array elements are passed to the compare function using pointers, involving extra stack handling. When function objects are used, the target person doesn't vary within a single search task. Therefore, the target person could be passed to the constructor of the function object doing the comparison. This is in fact what happened in the expression target(base[idx]), where only one argument is passed to the operator()() member function of the target function object.

As noted, function objects play a central role in generic algorithms. In chapter 17 these generic algorithms are discussed in detail. Furthermore, in that chapter predefined function objects will be introduced, further emphasizing the importance of the function object concept.

9.10.1: Constructing manipulators

In chapter 5 we saw constructions like cout << hex << 13 << endl to display the value 13 in hexadecimal format. One may wonder by what magic the hex manipulator accomplishes this. In this section the construction of manipulators like hex is covered.

Actually the construction of a manipulator is rather simple. To start, a definition of the manipulator is needed. Let's assume we want to create a manipulator w10 which will set the field width of the next field to be written to the ostream object to 10. This manipulator is constructed as a function. The w10 function will have to know about the ostream object in which the width must be set. By providing the function with a ostream & parameter, it obtains this knowledge. Now that the function knows about the ostream object we're referring to, it can set the width in that object.

Next, it must be possible to use the manipulator in an insertion sequence. This implies that the return value of the manipulator must be a reference to an ostream object also.

From the above considerations we're now able to construct our w10 function:

    #include <ostream>
    #include <iomanip>

    std::ostream &w10(std::ostream &str)
    {
        return str << std::setw(10);
    }
The w10 function can of course be used in a `stand alone' mode, but it can also be used as a manipulator. E.g.,
        #include <iostream>
        #include <iomanip>

        using namespace std;

        extern ostream &w10(ostream &str);

        int main()
        {
            w10(cout) << 3 << " ships sailed to America" << endl;
            cout << "And " << w10 << 3 << " more ships sailed too." << endl;
        }
The w10 function can be used as a manipulator because the class ostream has an overloaded operator<<() accepting a pointer to a function expecting an ostream & and returning an ostream &. Its definition is:
    ostream& operator<<(ostream & (*func)(ostream &str))
    {
        return (*func)(*this);
    }

The above procedure does not work for manipulators requiring arguments: it is of course possible to overload operator<<() to accept an ostream reference and the address of a function expecting an ostream & and, e.g., an int, but while the address of such a function may be specified with the <<-operator, the arguments itself cannot be specified. So, one wonders how the following construction has been implemented:

    cout << setprecision(3)
In this case the manipulator is defined as a macro. Macro's, however, are the realm of the preprocessor, and may easily suffer from unwanted side-effects. In C++ programs they should be avoided whenever possible. The following section introduces a way to implement manipulators requiring arguments without resorting to macros, but using anonymous objects.

9.10.1.1: Manipulators requiring arguments

Manipulators taking arguments are implemented as macros: they are handled by the preprocessor, and are not available beyond the preprocessing stage. The problem appears to be that you can't call a function in an insertion sequence: in a sequence of operator<<() calls the compiler will first call the functions, and then use their return values in the insertion sequence. That will invalidate the ordering of the arguments passed to your <<-operators.

So, one might consider constructing another overloaded operator<<() accepting the address of a function receiving not just the ostream reference, but a series of other arguments as well. The problem now is that it isn't clear how the function will receive its arguments: you can't just call it, since that produces the abovementioned problem, and you can't just pass its address in the insertion sequence, as you normally do with a manipulator....

However, there is a solution, based on the use of anonymous objects:

Here is an example of a little program using such a home-made manipulator expecting multiple arguments:
    #include <iostream>
    #include <iomanip>

    class Align
    {
        unsigned d_width;
        std::ios::fmtflags d_alignment;

        public:
            Align(unsigned width, std::ios::fmtflags alignment);
            std::ostream &operator()(std::ostream &ostr) const;
    };

        Align::Align(unsigned width, std::ios::fmtflags alignment)
        :
            d_width(width),
            d_alignment(alignment)
        {}

        std::ostream &Align::operator()(std::ostream &ostr) const
        {
            ostr.setf(d_alignment, std::ios::adjustfield);
            return ostr << std::setw(d_width);
        }

    std::ostream &operator<<(std::ostream &ostr, Align const &align)
    {
        return align(ostr);
    }

    using namespace std;

    int main()
    {
        cout
            << "`" << Align(5, ios::left) << "hi" << "'"
            << "`" << Align(10, ios::right) << "there" << "'" << endl;
    }

    /*
        Generated output:

        `hi   '`     there'
    */
Note that in order to insert an anonymous Align object into the ostream, the operator<<() function must define a Align const & parameter (note the const modifier).

9.11: Overloadable operators

The following operators can be overloaded:
    +       -       *       /       %       ^       &       |
    ~       !       ,       =       <       >       <=      >=
    ++      --      <<      >>      ==      !=      &&      ||
    +=      -=      *=      /=      %=      ^=      &=      |=
    <<=     >>=     []      ()      ->      ->*     new     new[]
    delete  delete[]
Several operators have textual alternatives:

textual alternative operator

and &&
and_eq &=
bitand &
bitor |
compl ~
not !
not_eq !=
or ||
or_eq |=
xor ^
xor_eq ^=

`Textual' alternatives of operators are also overloadable (e.g., operator and()). However, note that textual alternatives are not additional operators. So, within the same context operator&&() and operator and() can not both be overloaded.

Several of these operators may only be overloaded as member functions within a class. This holds true for the '=', the '[]', the '()' and the '->' operators. Consequently, it isn't possible to redefine, e.g., the assignment operator globally in such a way that it accepts a char const * as an lvalue and a String & as an rvalue. Fortunately, that isn't necessary either, as we have seen in section 9.3.

Finally, the following operators are not overloadable at all:

    .       .*      ::      ?:      sizeof  typeid