!!!Chapter 14 Overloaded Operations and Conversions (14.6 ~ 14.9)

14.6 Member Access Operators

To support pointer like classes, E.G. iterator, the language allows the dereference (*) and arrow(->) operators to be overloaded.

Operator arrow must be defined as a class member function. The dereference operator is not required to be a member, but it is usually right to make it a member as well.

Building a safer Pointer

The dereference and arrow operators are often used in classes that implement smart pointers. E.G.

// private class for use by ScreenPtr only
class ScrPtr {
    friend class ScreenPtr;
    Screen *sp;
    size_t use;
    ScrPtr(Screen *p): sp(p), use(1) { }
    ~ScrPtr () {delete sp;}
};
The ScreenPtr class manages the use count:
class ScreenPtr {
public:
    ScreenPtr (Screen *p) : ptr(new ScrPtr(p)) {}
    ScreenPtr(const ScreenPtr& orig) : 
        ptr(orig.ptr) { ++ptr->use; }
    ScreenPtr& operator= (const ScreenPtr &);
    ~ScreenPtr() { if (ptr->use==0) delete ptr; }
private:
    ScrPtr ptr;
}; 

Because there is no default constructor, every object of type ScreenPtr must provide an initializer. So ScreenPtr will always point to an actual value.

An attempt to define a ScreenPtr with no initializer is in error:

ScrenPtr p1;                    // error: ScreenPtr has no default constructor
ScreenPtr ps(new Screen(4,4));  //ok

Supporting Pointer Operations

Our class should support fundamental pointer operations (dereference and arrow):

class ScreenPtr {
public:
    Screen &operator*() { return *ptr->sp; }
    Screen *operator->() { return ptr->sp; }
    const Screen &operator*() const { return *ptr->sp; }
    const Screen *operator->() const { return ptr->sp; }
private:
    ScrPtr *ptr;
};

Overloading the Dereference Operator

The dereference operator is a unary operator. it is defined as a member so it has no explicit parameters. The operator returns a reference to theScreento which this Screenptr points.

Overloading the Arrow Operator

Though the arrow appear to be a binary operator, it takes no explicit parameter.

When we write 

point -> action();

precedence rules make it equivalent to writing:

(point->action) ();

The compiler evaluates this code as follows:
1. If point is a pointer to a class object that has a member named action, then the compiler writes code to call the action member of that object.

2. Otherwise, if point is an object of a class that definesoperator->, thenpoint->action is the same aspoint.operator->()->action. That is we executeoperator->() on point and then repeat these three steps, using the result of executing operator-> on point.

3. Otherwise, the code is in error.

Using Overloaded Arrow

ScreenPtr p(&myScreen);
p->display(cout);

So the actual code is: (p.operator->())->display

Constraints on the Return from Overloaded Arrow

The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow.

If the return type is a pointer, then the built-in arrow operator is applied to that pointer.

If the return value is another object of class type(or reference to such an object), then the operator is applied recursively.

14.7 Increment and Decrement Operators

The increment and decrement operators are most often used for classes that provide pointerlike behavior on the elements of a sequence.

class CheckedPtr {
public:
    CheckedPtr (int *b, int *e) : beg(b), end(e), curr(b) {}
private:
    int* beg;
    int* end;
    int* curr;
};

The end points to one past the last element!

Defining the Increment/Decrement Operators

It is not mendatory, but we prefer to make increment/decrement operators class members.

We could define two versions of increment/decrement operators: prefix and postfix.

Defining Prefix Increment/Decrement Operators

class CheckedPtr {
public:
    CheckedPtr& operator++();
    CheckedPtr& operator--();
};

For consistency with the built-in operators, the prefix operations should return a reference to the incremented or decremented object.

CheckedPtr& CheckedPtr::operator++()
{
    if (curr == end)
         throw out_of_range
              ("increment past the end");
    ++curr;
    return *this;
}

The increment/decrement ensure user cannot exceed the boundary.

CheckedPtr& CheckedPtr::operator--()
{
    if (curr == beg)
         throw out_of_range
              ("increment past the beginning");
    --curr;
    return *this;
}

Differentiating Prefix and Postfix Operators

To distinguish Prefix and Postfix, the postfix operator functions take an extra (unused) parameter of typeint. When we use the postfix operator, the compiler supplies 0 as the argument for this parameter.

Defining the Postfix Operator

class CheckedPtr {
public:
    CheckedPtr operator++ (int) ;
    CheckedPtr operator-- (int) ;
};

For consistency with the built-in operators, the postfix operators should return the old value.That value is returned as a value, not a reference.

CheckedPtr CheckedPtr::operator++(int)
{
    CheckedPtr ret(*this);
    ++*this;           // use the prefix ++ function
    return ret;
}

CheckedPtr CheckedPtr::operator--(int)
{
    CheckedPtr ret(*this);
    --*this;           // use the prefix -- function
    return ret;
}

The int parameter is not used, so we do not give it a name.

Calling the Postfix Operators Explicitly

We can explicitly call an overloaded operator rather than using it as an operator in an expression. P 509

If we want to call the postfix version using a function call, we must pass a value for the integer argument:

CheckPtr parr(ia, ia + size);
parr.operator++(0);        // call postfix operator++
parr.operator++();         // call prefix operator++

Ordinarily, it is better to define both prefix and postfix versions!

14.8 Call Operator and Function Objects (Functor)

The function-call operator can be overloaded for objects of class type. Typically, the call operator is overloaded for classes that represent an operation. E.G.

struct absInt {
    int operator() (int val) {
        return val<0 ? -val : val;
    }
};

We use the call operator by applying an argument list to an object of the class type, in a way that looks like a function call:

int i = -42;
absInt absObj;
unsigned int ui = absObj(i);    // calls absInt::operator(int)

The function-call operator must be declared as a member function. A class may define multiple versions of the call operator, each of which differs as to the number or types of their parameters.

Objects of class types that define the call operator are often referred to as function objects -- they are objects that act like functions.

14.8.1 Using Function Objects with Library Algorithms

Function objects are most often used as arguments to the generic algorithms.

Function way to calc words >= 6:

bool GT6 (const string &s)
{
    return s.size() >= 6;
}

vector<string>::size_type wc = 
         count_if(words.begin(), words.end(), GT6);    //we do not use GT6() as the third parameter

Function Objects Can Be More Flexible than Functions

The count_if algorithm runs a function that takes a single parameter and returns abool. Ideally, we'd pass both the string and the size we wanted to test.

We could gain the flexibility by defining GT6 as a class with a function call member:

class GT_cls {
public:
    GT_cls (size_t val = 0) : bound (val) {}
    bool operator(const string & s) { return s.size() >= bound; }
private:
    std::string::size_type bound;
};

Using a GT_cls Function Object

cout << count_if(words.begin(), words.end(), GT_cls(6)) << endl;

This call passes a temporary object of type GT_cls. Each time count_if calls its function parameter, it uses the call operator from GT_cls. That call operator tests the size of its string argument against the value in bound.

Another way to write the above code:

GT_cls GT6(6);     //create a GT_cls object
cout << count_if(words.begin(), words.end(), GT6) << endl;   //use this object in the algorithm

We cannot add () after GT6!

Otherwise, it will give error:     IntelliSense: function "GT_cls::operator()" cannot be called with the given argument list.         (VC 2010)

14.8.2 Library-Defined Function Objects

The library function-object types are defined in the functional header. P 534

Each Class Represent a Given Operator

Each of the library function-object classes represents an operator -- that is, each class defines the call operator that applies the named operation. E.G.plusis a template type that represents addition operator.

There are two unary function-object classes: unary minus (negate <Type>) and logical NOT (logical_not<Type>). The remaining library function objects are binary function-object classes.

The Template Type Represents the Operand(s) Type

Each of the function-object classes is a class template to which we supply a single type.

A class template is a class that can be used on a variety of types. The template type for the function-object classes specifies the parameter type for the call operator. E.G.

plus<int> intAdd;         //function-object that add two ints
negate<int>   intNegate;  //function-object that negate int value
int sum = intAdd(10, 20);
sum = intAdd(10, intNegate(10));

Using a Library Function Object with the Algorithms

Function objects are often used to override the default operator used by an algorithm. E.G.

sort(svec.begin(), svec.end(), greater<string>());     

Here we have () after greater<string>, because we create a temporary object of type greater<string>

14.8.3 Function Adaptors for Function Objects

The standard library provides a set of function adaptors to specialize and extend both unary and binary function objects. There are two categories:

1. Binders: a binder is a function adaptor that converts a binary function object into a unary function object by binding one of the operands to a given value.

2. Negator: a negator is a function adaptor that reverses the truth value of a predicate function object.

The library defines two binder adaptors: bind1st and bind2nd. bind1st will give value to first argument, bind2nd will give value to the second.

E.G. to count elements <=10:

count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));

The library defines two negators: not1and not2. not1 will reverse the truth value of a unary predicate function, not2 will reverse the truth value of a binary predicate function.

E.G.

count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(), 10)));

we first transfer less_equal to a unary function, so we will use not1 later!

14.9 Conversions and Class Type

As 12.4.4, a nonexplicit constructor can convert an argument type into a class type. We can also define conversions from the class type. As with other conversions, the compiler will apply this conversion automatically.

14.9.1 Why Conversions Are Useful

Supporting Mixed-Type Expressions

int operator+(int, const SmallInt&);
int operator+(const SmallInt&, int);
int operator+(const SmallInt&, const SmallInt&);

The problem of the above implementation is: it converts all arithmetic types -- even those bigger than int(float, unsigned int...) -- to int and does an int addition.

Conversions Reduce the Number of Needed Operators

If we define a conversion from SmallInt to int, then we won't need to define any arithmetic, relational, or equality operators. Given a conversion to int, a SmallInt object could be used anywhere an int could be used.

SmallInt si(3);
si + 3.1415926;

In the above expression:

1. We convert si to an int.

2. we convert the int to double and do addition, finally we yield a double value.

14.9.2 Conversion Operators

The conversion operator defines a conversion that converts a value of a class type to a value of some other type.

A conversion operator is declared in the class body by specifying the keyword operator followed by the type that is the target type of the conversion:

operator type();
class SmallInt {
public:
    operator int() const { return val; }
private:
    std::size_t val;
};

Here type can be built-in type, class type, or a name defined by a typedef.

Conversion functions can be defined on pointer/reference types, but cannot be defined on an array or function type.

A conversion function must be a member function. The function may not specify a return type, and the parameter list must be empty.

Although a conversion function does not specify a return type, each conversion function must explicitly return a value of the named type. E.G.operator int returns anint.

Conversion operations ordinarily should not change the object they are converting. As a result, conversion operators usually should be defined as const members.

Implicit Class-Type Conversions

Once a conversion exists, the compiler will call it automatically:

1. In expression:

SmallInt si;
double dval;
si >= dval;     //si convert to in then convert to double

2. In conditions:

if (si)        //si converted to int and then convert to bool

3. When passing argument to or returning values from a function:

int calc( int );
SmallInt si;
int i = calc( si );    // convert si to int and call calc

4. As operands to overloaded operators:

// convert si to int then call operator << on the int value
cout << si << endl;

5. In an explicit cast:

int ival;
SmallInt si = 3.1415926;
// instruct compiler to call si to int
ival = static_cast<int>(si) + 3;

Class-Type Conversions and Standard Conversions

We can have both Class-Type conversion and Standard Conversion work together to generate the required type. (Case 1 in the above scenario.)

Only One Implicit Class-Type Conversion May Be Applied

An implicit class-type conversion may not be followed by another implicit class-type conversion. If more than one implicit class-type conversion is needed then the code is in error. E.G.class A -> class B -> type C is an error!

Standard Conversions Can Precede a Class-Type ConversionP 540

Typc A -> Type B -> class C is supported. Type A -> Type B through a standard conversion.

Left 14.9.3 ~ 14.9.5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值