Google
 

Tuesday, February 15, 2011

C++ Primer, Fourth Edition:Notes Chapter 14. Overloaded Operations and Conversions

  • C++ lets us redefine the meaning of the operators when applied to objects of class type. It also lets us define conversion operations for class types. Class-type conversions are used like the built-in conversions to implicitly convert an object of one type to another type when needed
  • Operator overloading allows the programmer to define versions of the operators for operands of class type
  • Through operator overloading, we can redefine most of the operators from Chapter 5 to work on objects of class type. Judicious use of operator overloading can make class types as intuitive to use as the built-in types
  • Allowing programs to use expressions rather than named functions can make the programs much easier to write and read.

Section 14.1 Defining an Overloaded Operator

  • Overloaded operators are functions with special names: the keyword operator followed by the symbol for the operator being defined. Like any other function, an overloaded operator has a return type and a parameter list
  • With the exception of the function-call operator, an overloaded operator has the same number of parameters (including the implicit this pointer for member functions) as the operator has operands. The function-call operator takes any number of operands
  • Overloaded Operator Names

      Table 14.1. Overloadable Operators

      +

      -

      *

      /

      %

      ^

      &

      |

      ~

      !

      ,

      =

      <

      >

      <=

      >=

      ++

      --

      <<

      >>

      ==

      !=

      &&

      ||

      +=

      -=

      /=

      %=

      ^=

      &=

      |=

      *=

      <<=

      >>=

      []

      ()

      ->

      ->*

      new

      new []

      delete

      delete[]


      Table 14.2. Operators That Cannot Be Overloaded

      ::

      .*

      .

      ?:


    • New operators may not be created by concatenating other legal symbols
  • Overloaded Operators Must Have an Operand of Class Type
    • The meaning of an operator for the built-in types may not be changed.Nor may additional operators be defined for the built-in data types
    • An overloaded operator must have at least one operand of class or enumeration (Section 2.7, p. 62) type. This rule enforces the requirement that an overloaded operator may not redefine the meaning of the operators when applied to objects of built-in type
  • Precedence and Associativity Are Fixed
    • The precedence (Section 5.10.1, p. 168), associativity, or number of operands of an operator cannot be changed
    • Four symbols (+, -, *, and &) serve as both unary and binary operators. Either or both of these operators can be overloaded. Which operator is being defined is controlled by the number of operands. Default arguments for overloaded operators are illegal, except for operator(), the function-call operator
  • Short-Ciruit Evaluation Is Not Preserved
    • Overloaded operators make no guarantees about the order in which operands are evaluated. In particular, the operand-evaluation guarantees of the built-in logical AND, logical OR (Section 5.2, p. 152), and comma (Section 5.9, p. 168) operators are not preserved. Both operands to an overloaded version of && or || are always evaluated. The order in which those operands are evaluated is not stipulated. The order in which the operands to the comma are evaluated is also not defined. For this reason, it is usually a bad idea to overload &&, ||, or the comma operator.
  • Class Member versus Nonmember
    • Most overloaded operators may be defined as ordinary nonmember functions or as class member functions
    • Overloaded functions that are members of a class may appear to have one less parameter than the number of operands. Operators that are member functions have an implicit this parameter that is bound to the first operand
  • Operator Overloading and Friendship
    • When operators are defined as nonmember functions, they often must be made friends (Section 12.5, p. 465) of the class(es) on which they operate
  • Using Overloaded Operators
    • We can use an overloaded operator in the same way that we'd use the operator on operands of built-in type
    • We also can call an overloaded operator function in the same way that we call an ordinary function: We name the function and pass an appropriate number of arguments of the appropriate type
      • cout << item1 + item2 << endl;
         
        // equivalent direct call to nonmember operator function
        // This call has the same effect as the expression that added item1 and item2.
        cout << operator+(item1, item2) << endl;

    • We call a member operator function the same way we call any other member function: We name an object on which to run the function and then use the dot or arrow operator to fetch the function we wish to call passing the required number and type of arguments. In the case of a binary member operator function, we must pass a single operand


        item1 += item2;            // expression based "call"
        item1.operator+=(item2);   // equivalent call to member operator function


  • 14.1.1. Overloaded Operator Design

    • Don't Overload Operators with Built-in Meanings

      • The assignment, address of, and comma operators have default meanings for operands of class types. If there is no overloaded version specified, the compiler defines its own version of these operators
      • The synthesized assignment operator (Section 13.2, p. 482) does memberwise assignment: It uses each member's own assignment operator to assign each member in turn
      • By default the address of (&) and comma (,) operators execute on class type objects the same way they do on objects of built-in type. The address of operator returns the address in memory of the object to which it is applied. The comma operator evaluates each expression from left to right and returns the value of its rightmost operand
      • The built-in logical AND (&&) and OR(||) operators apply short-circuit evaluation (Section 5.2, p. 152). If the operator is redefined, the short-circuit nature of the operators is lost
      • It is usually not a good idea to overload the comma, address-of, logical AND, or logical OR operators. These operators have built-in meanings that are useful and become inaccessible if we define our own versions
      • We sometimes must define our own version of assignment. When we do so, it should behave analogously to the synthesized operators: After an assignment, the values in the left-hand and right-hand operands should be the same and the operator should return a reference to its left-hand operand. Overloaded assignment should customize the built-in meaning of assignment, not circumvent it

    • Most Operators Have No Meaning for Class Objects

      • Operators other than assignment, address-of, and comma have no meaning when applied to an operand of class type unless an overloaded definition is provided. When designing a class, we decide which, if any, operators to support
      • The best way to design operators for a class is first to design the class' public interface. Once the interface is defined, it is possible to think about which operations should be defined as overloaded operators. Those operations with a logical mapping to an operator are good candidates

    • Compound Assignment Operators

      • If a class has an arithmetic (Section 5.1, p. 149) or bitwise (Section 5.3, p. 154) operator, then it is usually a good idea to provide the corresponding compound-assignment operator as well

    • Equality and Relational Operators

      • Classes that will be used as the key type of an associative container should define the < operator. The associative containers by default use the < operator of the key type. Even if the type will be stored only in a sequential container, the class ordinarily should define the equality (==) and less-than (<) operators. The reason is that many algorithms assume that these operators exist
      • If the class defines the equality operator, it should also define !=. Users of the class will assume that if they can compare for equality, they can also compare for inequality. The same argument applies to the other relational operators as well. If the class defines <, then it probably should define all four relational operators (>, >=, <, and <=)

        Caution: Use Operator Overloading Judiciously


        Each operator has an associated meaning from its use on the built-in types. Binary +, for example, is strongly identified with addition. Mapping binary + to an analogous operation for a class type can provide a convenient notational shorthand. For example, the library string type, following a convention common to many programming languages, uses + to represent concatenation"adding" one string to the other.


        Operator overloading is most useful when there is a logical mapping of a built-in operator to an operation on our type. Using overloaded operators rather than inventing named operations can make our programs more natural and intuitive. Overuse or outright abuse of operator overloading can make our classes incomprehensible.


        Obvious abuses of operator overloading rarely happen in practice. As an example, no responsible programmer would define operator+ to perform subtraction. More common, but still inadvisable, are uses that contort an operator's "normal" meaning to force a fit to a given type. Operators should be used only for operations that are likely to be unambiguous to users. An operator with ambiguous meaning, in this sense, is one that supports equally well a number of different interpretations.









        When the meaning of an overloaded operator is not obvious, it is better to give the operation a name. It is also usually better to use a named function rather than an operator for operations that are rarely done. If the operation is unusual, the brevity of using an operator is unnecessary.


    • Choosing Member or Nonmember Implementation

      • The following guidelines can be of help when deciding whether to make an operator a member or an ordinary nonmember function

        • The assignment (=), subscript ([]), call (()), and member access arrow (->) operators must be defined as members. Defining any of these operators as a nonmember function is flagged at compile time as an error
        • Like assignment, the compound-assignment operators ordinarily ought to be members of the class. Unlike assignment, they are not required to be so and the compiler will not complain if a nonmember compound-assignment operator is defined
        • Other operators that change the state of their object or that are closely tied to their given typesuch as increment, decrement, and dereferenceusually should be members of the class
        • Symmetric operators, such as the arithmetic, equality, relational, and bitwise operators, are best defined as ordinary nonmember functions


Section 14.2 Input and Output Operators



  • Classes that support I/O ordinarily should do so by using the same interface as defined by the iostream library for the built-in types. Thus, many classes provide overloaded instances of the input and output operators
  • 14.2.1. Overloading the Output Operator <<

    • To be consistent with the IO library, the operator should take an ostream& as its first parameter and a reference to a const object of the class type as its second. The operator should return a reference to its ostream parameter

      // general skeleton of the overloaded output operator
      ostream&
          operator <<(ostream& os, const ClassType &object)
      {
          // any special logic to prepare object
       
          // actual output of members
          os << // ...
       
              // return ostream object
              return os;
      }


  • The Sales_item Output Operator
  • Output Operators Usually Do Minimal Formatting

    • Class designers face one significant decision about output: whether and how much formatting to perform
    • Generally, output operators should print the contents of the object, with minimal formatting. They should not print a newline
    • The output operators for the built-in types do little if any formatting and do not print newlines. Given this treatment for the built-in types, users expect class output operators to behave similarly. By limiting the output operator to printing just the contents of the object, we let the users determine what if any additional formatting to perform. In particular, an output operator should not print a newline. If the operator does print a newline, then users would be unable to print descriptive text along with the object on the same line. By having the output operator perform minimal formatting, we let users control the details of their output

  • IO Operators Must Be Nonmember Functions

    • When we define an input or output operator that conforms to the conventions of the iostream library, we must make it a nonmember operator
    • We cannot make the operator a member of our own class.If we did, then the left-hand operand would have to be an object of our class type
    • Instead, if we want to use the overloaded operators to do IO for our types, we must define them as a nonmember functions. IO operators usually read or write the nonpublic data members. As a consequence, classes often make the IO operators friends

  • 14.2.2. Overloading the Input Operator >>

    • Similar to the output operator, the input operator takes a first parameter that is a reference to the stream from which it is to read, and returns a reference to that same stream. Its second parameter is a nonconst reference to the object into which to read. The second parameter must be nonconst because the purpose of an input operator is to read data into this object
    • A more important, and less obvious, difference between input and output operators is that input operators must deal with the possibility of errors and end-of-file
    • The Sales_item Input Operator


        istream& operator>>(istream& in, Sales_item& s)
        {
            double price;
            in >> s.isbn >> s.units_sold >> price;
            // check that the inputs succeeded
            if (in)
                s.revenue = s.units_sold * price;
            else
                s = Sales_item(); // input failed: reset object to default state
            return in;
        }

    • Errors During Input

      • The kinds of errors that might happen include:
      • Any of the read operations could fail because an incorrect value was provided.
      • Any of the reads could hit end-of-file or some other error on the input stream
      • Rather than checking each read, we check once before using the data we read

    • Handling Input Errors

      • If an input operator detects that the input failed, it is often a good idea to make sure that the object is in a usable and consistent state. Doing so is particularly important if the object might have been partially written before the error occurred
      • When designing an input operator, it is important to decide what to do about error-recovery, if anything

    • Indicating Errors

      • In addition to handling any errors that might occur, an input operator might need to set the condition state (Section 8.2, p. 287) of its input istream parameter
      • Some input operators do need to do additional checking.
      • Usually an input operator needs to set only the failbit. Setting eofbit would imply that the file was exhausted, and setting badbit would indicate that the stream was corrupted. These errors are best left to the IO library itself to indicate.

Section 14.3 Arithmetic and Relational Operators



  • Ordinarily, we define the arithmetic and relational operators as nonmember functions
  • Note that to be consistent with the built-in operator, addition returns an rvalue, not a reference
  • An arithmetic operator usually generates a new value that is the result of a computation on its two operands. That value is distinct from either operand and is calculated in a local variable. It would be a run-time error to return a reference to that variable
  • Classes that define both an arithmetic operator and the related compound assignment ordinarily ought to implement the arithmetic operator by using the compound assignment
  • It is simpler and more efficient to implement the arithmetic operator (e.g., +) in terms of the compound-assignment operator (e.g., +=) rather than the other way around. If we implemented += by calling +, then += would needlessly create and destroy a temporary to hold the result from +.
  • 14.3.1. Equality Operators

    • Ordinarily, classes in C++ use the equality operator to mean that the objects are equivalent. That is, they usually compare every data member and treat two objects as equal if and only if all corresponding members are the same


        inline bool
            operator==(const Sales_item &lhs, const Sales_item &rhs)
        {
            // must be made a friend of Sales_item
            return lhs.units_sold == rhs.units_sold &&
                lhs.revenue == rhs.revenue &&
                lhs.same_isbn(rhs);
        }
        inline bool
            operator!=(const Sales_item &lhs, const Sales_item &rhs)
        {
            return !(lhs == rhs); // != defined in terms of operator==
        }

    • The definition of these functions is trivial. More important are the design principles that these functions embody

      • If a class defines the == operator, it defines it to mean that two objects contain the same data
      • If a class has an operation to determine whether two objects of the type are equal, it is usually right to define that function as operator== rather than inventing a named operation. Users will expect to be able to compare objects using ==, and doing so is easier than remembering a new name
      • If a class defines operator==, it should also define operator!=. Users will expect that if they can use one operator, then the other will also exist
      • The equality and inequality operators should almost always be defined in terms of each other. One operator should do the real work to compare objects. The other should call the one that does the real work

    • Classes that define operator== are easier to use with the standard library. Some algorithms, such as find, use the == operator by default. If a class defines ==, then these algorithms can be used on that class type without any specialization

  • 14.3.2. Relational Operators

    • Classes for which the equality operator is defined also often have relational operators. In particular, because the associative containers and some of the algorithms use the less-than operator, it can be quite useful to define an operator<.
    • Because the logical definition of < is inconsistent with the logical definition of ==, it is better not to define < at all. We'll see in Chapter 15 how to use a separate named function to compare Sales_items when we want to store them in an associative container
    • The associative containers, as well as some of the algorithms, use the < operator by default. Ordinarily, the relational operators, like the equality operators, should be defined as nonmember functions



Section 14.4 Assignment Operators



  • We covered the assignment of one object of class type to another object of its type in Section 13.2 (p. 482). The class assignment operator takes a parameter that is the class type. Usually the parameter is a const reference to the class type. However, the parameter could be the class type or a nonconst reference to the class type. This operator will be synthesized by the compiler if we do not define it ourselves. The class assignment operator must be a member of the class so the compiler can know whether it needs to synthesize one
  • Additional assignment operators that differ by the type of the right-hand operand can be defined for a class type
  • Assignment operators can be overloaded. Unlike the compound-assignment operators, every assignment operator, regardless of parameter type, must be defined as a member function


      // illustration of assignment operators for class string
      class string {
      public:
          string& operator=(const string &);      // s1 = s2;
          string& operator=(const char *);        // s1 = "str";
          string& operator=(char);                // s1 = 'c';
          // ....
       };

  • Assignment Should Return a Reference to *this

    • The string assignment operators return a reference to string, which is consistent with assignment for the built-in types. Moreover, because assignment returns a reference there is no need to create and destroy a temporary copy of the result. The return value is usually a reference to the left-hand operand
    • Ordinarily, assignment operators and compound-assignment operators ought to return a reference to the left-hand operand

Section 14.5 Subscript Operator



  • Classes that represent containers from which individual elements can be retrieved usually define the subscript operator, operator[]. The library classes, string and vector, are examples of classes that define the subscript operator

  • The subscript operator must be defined as a class member function

  • Providing Read and Write Access


    • One complication in defining the subscript operator is that we want it to do the right thing when used as either the left- or right-hand operand of an assignment

    • It is also a good idea to be able to subscript const and nonconst objects. When applied to a const object, the return should be a const reference so that it is not usable as the target of an assignment

    • Ordinarily, a class that defines subscript needs to define two versions: one that is a nonconst member and returns a reference and one that is a const member and returns a const reference


  • Prototypical Subscript Operator



    • #include "stdafx.h"
      #include <vector>
      using namespace  std;
      class Foo {
      public:
          int &operator[] (const size_t);
          const int &operator[] (const size_t) const;
          // other interface members
      private:
          vector<int> data;
          // other member data and private utility functions
      };
       
      int& Foo::operator[] (const size_t index)
      {
          return data[index];  // no range checking on index
      }
      const int& Foo::operator[] (const size_t index) const
      {
          return data[index];  // no range checking on index
      }

Section 14.6 Member Access Operators


2011.2.21 here


Section 14.7 Increment and Decrement Operators


Section 14.8 Call Operator and Function Objects


Section 14.9 Conversions and Class Types

C++ Primer, Fourth Edition:Notes Chapter 17. Tools for Large Programs

 

 

 

Section 17.1 Exception Handling

Section 17.2 Namespaces

Section 17.3 Multiple and Virtual Inheritance

C++ Primer, Fourth Edition:Notes Chapter 16. Templates and Generic Programming

 

 

 

Section 16.1 Template Definitions

Section 16.2 Instantiation

Section 16.3 Template Compilation Models

Section 16.4 Class Template Members

Section 16.5 A Generic Handle Class

Section 16.6 Template Specializations

Section 16.7 Overloading and Function Templates

C++ Primer, Fourth Edition:Notes Chapter 15. Object-Oriented Programming

 

 

Section 15.1 OOP: An Overview

Section 15.2 Defining Base and Derived Classes

Section 15.3 Conversions and Inheritance

Section 15.4 Constructors and Copy Control

Section 15.5 Class Scope under Inheritance

Section 15.6 Pure Virtual Functions

Section 15.7 Containers and Inheritance

Section 15.8 Handle Classes and Inheritance

Section 15.9 Text Queries Revisited

C++ Primer, Fourth Edition:Notes Chapter Chapter 18. Specialized Tools and Techniques

 

 

Section 18.1 Optimizing Memory Allocation

Section 18.2 Run-Time Type Identification

Section 18.3 Pointer to Class Member

Section 18.4 Nested Classes

Section 18.5 Union: A Space-Saving Class

Section 18.6 Local Classes

Section 18.7 Inherently Nonportable Features