Previous Table of Contents Next


For further discussion of virtual functions and object-oriented programming, see section 6.3.7 and section 6.4.3.

The key implementation idea was that the set of virtual functions in a class defines an array of pointers to functions so that a call of a virtual function is simply an indirect function call through that array. There is one array per class and one pointer to such an array in each object of a class that has virtual functions.

I don’t remember much interest in virtual functions at the time. It may be that I didn’t explain the concepts involved well. But the main reactions I received from people in my immediate vicinity were indifference and skepticism. A common opinion was that virtual functions were simply a kind of crippled pointer to function and thus redundant. Worse, it was sometimes argued that a well-designed program wouldn’t need the extensibility and openness provided by virtual functions so that proper analysis would show which non-virtual functions could be called directly. Therefore, the argument went, virtual functions were simply a form of inefficiency. Clearly I disagreed and added virtual functions anyway.

6.3.3.3. Overloading

Several people had asked for the ability to overload operators. Operator overloading “looked neat” and I knew from experience with Algol68 how the idea could be made to work. However, I was reluctant to introduce the notion of overloading into C++ for the following reasons:

  Overloading was reputed to be hard to implement so that compilers would grow to monstrous size.
  Overloading was reputed to be hard to teach and hard to define precisely so that manuals and tutorials would grow to monstrous size.
  Code written using operator overloading was reputed to be inherently inefficient.
  Overloading was reputed to make code incomprehensible.

If the last two reasons were true, then C++ would be better off without overloading. If the first two were true, then I didn’t have the resources to provide overloading.

However, if all these conjectures were false, then overloading would solve some real problems for C++ users. There were people who would like to have complex numbers, matrices, and APL-like vectors in C++. There were people who would like range-checked arrays, multidimensional arrays, and strings in C++. There were at least two separate applications for which people wanted to overload logical operators such as | (or), & (and), and ^ (exclusive or). The way I saw it, the list was long and would grow with the size and the diversity of the C++ user population. My answer to the potential problem of making code obscure was that several of my friends, whose opinions I valued and whose experience was measured in decades, claimed that their code would become cleaner if they had overloading. So what if one can write obscure code with overloading? It is possible to write obscure code in any language. It matters more how a feature can be used well than how it can be misused.

Next, I convinced myself that overloading wasn’t inherently inefficient (Ellis & Stroustrup, 1990; Stroustrup, 1984c). The details of the overloading mechanism were mostly worked out on my blackboard and those of Stu Feldman, Doug McIlroy, and Jonathan Shopiro.

Thus, having worked out a solution to the efficiency problem, I needed to concern myself with the issues of compiler and language complexity. I first observed that use of classes with overloaded operators, such as complex and string, was quite easy and didn’t put a major burden on the programmer. Next, I wrote the manual sections to prove that the added complexity wasn’t a serious issue; the manual needed less than a page and a half extra (out of a 42-page manual). Finally, I did the first implementation in 2 hours using only 18 lines of extra code in Cfront, and I felt I had demonstrated that the fears about definition and implementation complexity were somewhat exaggerated.

Naturally, all these issues were not really tackled in this strict sequential order. However, the emphasis of the work did start with utility issues and slowly drifted to implementation issues. The overloading mechanisms were described in detail in “Operator Overloading in C++” (Stroustrup, 1984c) and examples of classes using the mechanisms were written up (Rose & Stroustrup, 1984; Shopiro, 1985).

In retrospect, I underestimated the complexity of the definition and implementation issues and compounded these problems by trying to isolate overloading mechanisms from the rest of the language semantics. The latter was done out of misguided fear of confusing users. In particular, I required that a declaration

   overload print;

should precede declarations of an overloaded function print, such as

   void print(int);
   void print(const char*);

I also insisted that ambiguity control should happen in two stages so that resolutions involving built-in operators and conversions would always take precedence over resolutions involving user-defined operations. Maybe the latter was inevitable given the concern for C compatibility and the chaotic nature of the C conversion rules for built-in types. These conversions do not constitute a directed acyclic graph; for example, implicit conversions are allowed both from int to float and from float to int. However, the rules for ambiguity resolution were too complicated, caused surprises, and had to be revised for Release 2.0. I still consider these rules too complex but do not see scope for more than minor adjustments.

Requiring explicit overload declarations was plain wrong and the requirement was dropped in Release 2.0.


Previous Table of Contents Next