!!!Chapter 14 Overloaded Operations and Conversions (14.1 ~ 14.5)

14.1 Defining an Overloaded Operator

Overloaded operators has a return type and a parameter list:

Sales_item operator+(const Sales_item &, const Sales_item&)

Normally, overloaded operator has the same number of parameters as the operator has operands(except function-call operator)

Overloaded Operator NamesP 507

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 overloaded operators be defined for the built-in data types (array):

// error: cannot redefine built-in operator for ints
int operator+(int, int);

An overloaded operator must have at least one operand of class or enumeration type.

Precedence and Associativity Are Fixed

The precedence, 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-Circuit Evaluation Is Not Preserved

The operand-evaluation guarantees of the built-in logical AND, logical OR, and coma operators are not preserved.

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 class member functions.

Overloaded functions that are member functions may appear to have one less parameter.(The implicit this parameter).

Ordinarily we define the arithmetic and relational operators as nonmember functions and we define assignment operators as members.

// member binary operator
Sales_item& Sales_item::operator+=(const Sales_item&);
// nonmember binary operator
Sales_item operator+ (const Sales_item&, const Sales_item&);

Addition yields an rvalue and compound assignment returns a reference to the left-hand operand.

Operator Overloading and Friendship

When operators are defined as nonmember functions, they often must be made friends of the class on which they operate.

Sales_item defines one member operator and has three nonmember operators:

class Sales_item {
    friend std::istream& operator>>
           (std::istream&, Sales_item&);
    friend std::ostream& operator<<
           (std::ostream&, const Sales_item&);
public:
    Sales_item& operator+=(const Sales_item&);
};
Sales_item operator+ (const Sales_item&, const Sales_item&);

operator+ don't need to be a friend, it can be implemented using the public member operator+=

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:

cout << item1 + item2 << endl;

We can also call an overloaded operator function in the same way that we call an ordinary function:

cout << operator+(item1, item2) << endl;

It works the same for member function, but for binary operator, we must pass a single operand:

item1 += item2;
item1.operator+=(item2);

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:

1. The synthesized assignment operator does memberwise assignment.

2. By default the address of (&) and comma (,) operator execute on class type objects the same way they do on objects of built-in type. The address operator returns the address in memory of the object,the comma operator evaluates each expression from left to right and returns the value of its rightmost operand.

3. The built-in logical AND (&&) and OR(||) operators apply short-circuit evaluation. If the operator is redefined, the short-circuit nature of the operators is lost.

It is usually not a good idea to overload comma, address-of, logical AND, or logical OR operator. And we overload assignment operator when necessary.

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.

The best way to design operators for a class is first to design the class' public interface.

Operations with a logical mapping to an operator are good overloaded operator candidates:

1. An operation to test for equality should use operator==

2. Input and output are normally done by overloading the shift operators.

3. An operation to test whether the object is empty could be represented by the logical NOT operator, operator!

Compound Assignment Operator

If a class has an arithmetic or bitwise operator, then it is usually a good idea to provide the corresponding compound-assignment operator as well.

E.G. if you define + for the class, you should define +=

Equality and Relational Operators

classes that will be used as the key type of an associative container(map, set) should define the < operator.

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 operations exist. E.G. sort uses < and find uses ==.

If the class defines the equality operator, it should also define !=. And if we define <, then we should also define (>, >=, <, <=).

Choosing Member or Nonmember Implementation

Following guidelines can be used to decide whether to make an operator a member or an ordinary nonmember function:

1. The assignment(=), subscript( [ ] ), call( () ), and member access arrow( -> ) operators must be defined as member. (Otherwise, there will be compile time error)

2. Like assignment, the compound assignment operators ordinarily ought to be members of the class. (But there is no compile time error)

3. Other operators that change the state of their object or that are closely tied to their given type -- such as increment, decrement and dereference -- usually should be members of class.

4. Symmetric operators, such as the arithmetic, equality, relational, and bitwise operators are best defined as ordinary nonmemeber functions.

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.

14.2.1 Overloading the Output Operator <<

To be consistent with the IO library, the operator should take anostream& 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

The 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 objet
    return os;
} 

The Sales_item Output Operator

ostream&
operator<< (ostream& out, const Sales_item& s)
{
    out << s.isbn << "\t" << s.units_sold << "\t"
        << s.revenue << "\t" << s.avg_price();
    return os;
}

Output Operators Usually Do Minimal Formatting

Generally output operators should print the contents of the object, with minimal formatting.

They should not print a newline.

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:

Sales_item item;
item << cout;

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 >>

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. The second parameter is a nonconst reference to the object into which to read.

A more important, and less obvious, difference between input and output operator is that input operators must deal with the possibility of errors and EOF.

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 includes:

1. Any of the read operations could fail because an incorrect value was provided.

2. Any of the reads could hit EOF or some other error on the input stream.

Rather than checking each read, we check once before using the data we read:

if (in)
    s.revenue = s.units_sold * price;
else
    s = Sales_item();

If there was an error, we do not worry about which input failed. Instead, we reset the entire object as if it were an empty Sales_item.

Handling Input Errors

If an input operator encounters an error, it normally should ensure that the object being read into is left in a usable and consistent state.

In our Sales_item example, we avoid giving parameter an invalid state by resetting it to the empty state if an error occurs.

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 of its input istream parameter.

14.3 Arithmetic and Relational Operators

Ordinarily, we define arithmetic and relational operators as nonmember functions.

To be consistent with the built-in operator, addition returns an rvalue, not a reference.

Classes that define both an arithmetic operator and the related compound assignment ordinarily ought to implement the arithmetic operator by using the compound assignment.

Sales_item
operator+ (const Sales_item& lhs, const Sales_item& rhs)
{
    Sales_item ret(lhs);
    ret += rhs;
    return ret;
}

14.3.1 Equality Operators

Ordinarily, equality 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)
{
    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);
}

Design Principles:

1. If a class define the == operator, it defines it to mean that two objects contain the same data.

2. 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 invent a function.

3. If a class defines operator==, it should define operator!=.

4. The equality and inequality operators should almost always be defined in terms of each other.

classes that define ==, can use STL. E.G. find algorithm.

14.3.2 Relational Operators

Classes for which the equality operator is defined also often have relational operators.

Because the associative containers and some of the algorithms use the less-than operator, it can be quite useful to define anoperator<

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.

14.4 Assignment Operators

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. E.G.

class string {
public:
    string& operator= (const string &);  // s1 = s2
    string& operator= (const char *);    // s1 = "abc"
    string& operator= (char);            // s1 = 'a'
};

Assignment operators can be overloaded.

Unlike the compound assignment operators, every assignment operator, regardless of parameter type, must be defined as a member function.

Assignment Should Return a Reference to *this

Ordinarily, assignment operators and compound-assignment operators ought to return a reference to the left-hand operand.

14.5 Subscript Operator

Classes that represent containers from which individual elements can be retrieved usually define the subscript operator,operator[ ]

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

Providing Read and Write Access

Subscript should return a reference, so it can be used on either side of an assignment.

It is also a good idea to be able to subscript const and nonconst objects.

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

class Foo {
public:
    int &operator[] (const size_t);
    const int &operator[] (const size_t) const;
private:
    vector<int> data;
};

The subscript should look like:

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
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值