Cpp Chapter 15: Friends, Exceptions, and More Part1

15.1 Friends

) When you want to make class A's private members accessible to class B, let B be a friend class of A by declaring `friend class B;':

class A
{
public:
    friend class B;
    ...
}

Suppose we are going to have a Tv class and a Remote class now, so Tv's private members should be accessible to the Remote class. Also the Tv class should also include methods to switch up or down the channel and volume by 1 every time(just like the button on the Tv does), but you could only jump through channels with a Remote.
Speaking of sequence, you need to define the Tv class first:

// tv.h -- Tv and Remote classes
#ifndef TV_H_INCLUDED
#define TV_H_INCLUDED

class Tv
{
private:
    int state;
    int volume;
    int maxchannel;
    int channel;
    int mode;
    int input;
public:
    friend class Remote;
    enum {Off, On};
    enum {MinVal, MaxVal = 20};
    enum {Antenna, Cable};
    enum {TV, DVD};

    Tv(int s = Off, int mc = 125) : state(s), volume(5), maxchannel(mc), channel(2), mode(Cable), input(TV) {}
    void onoff() {state = (state==On) ? Off : On;}
    bool ison() {return state == On;}
    bool volup();
    bool voldown();
    void chanup();
    void chandown();
    void set_mode() {mode = (mode == Antenna) ? Cable : Antenna;}
    void set_input() {input = (input == TV) ? DVD : TV;}
    void settings() const;
};

class Remote
{
private:
    int mode;
public:
    Remote(int m = Tv::TV) : mode(m) {}
    bool volup(Tv & t) { return t.volup();}
    bool voldown(Tv & t) { return t.voldown();}
    void onoff(Tv & t) { t.onoff();}
    void chanup(Tv & t) { t.chanup();}
    void chandown(Tv & t) { t.chandown();}
    void set_chan(Tv & t, int c) { t.channel = c;}
    void set_mode(Tv & t) {t.set_mode();}
    void set_input(Tv & t) {t.set_input();}
};

#endif // TV_H_INCLUDED

Next comes the methods definition for the classes, notice that Remote methods are all defined to be inline:

// tv.cpp -- methods for the Tv class
#include <iostream>
#include "tv.h"

bool Tv::volup()
{
    if (volume < MaxVal)
    {
        volume++;
        return true;
    }
    return false;
}

bool Tv::voldown()
{
    if (volume > MinVal)
    {
        volume--;
        return true;
    }
    return false;
}

void Tv::chanup()
{
    if (channel < maxchannel)
        channel++;
    else
        channel = 1; // recursive
}

void Tv::chandown()
{
    if (channel > 1)
        channel--;
    else channel = maxchannel;
}

void Tv::settings() const
{
    using std::cout;
    using std::endl;
    cout << "Tv is " << (state == Off ? "Off" : "On") << endl;
    if (state == On)
    {
        cout << "Volume setting = " << volume << endl;
        cout << "Channel setting = " << channel << endl;
        cout << "Mode = " << (mode == Antenna ? "antenna" : "cable") << endl;
        cout << "Input = " << (input == TV ? "TV" : "DVD") << endl;
    }
}

Next comes a program that uses the features of the Remote and the Tv class:

// use_tv.cpp -- using the Tv and Remote classes
#include <iostream>
#include "tv.h"

int main()
{
    using std::cout;
    Tv s42;
    cout << "Initial settings for 42\" TV:\n";
    s42.settings();
    s42.onoff();
    s42.chanup();
    cout << "\nAdjusted settings for 42\" TV:\n";
    s42.settings();

    Remote grey;

    grey.set_chan(s42, 10);
    grey.volup(s42);
    grey.volup(s42);
    cout << "\n42\" settings after using remote:\n";
    s42.settings();

    Tv s58(Tv::On);
    s58.set_mode();
    grey.set_chan(s58, 28);
    cout << "\n58\" settings:\n";
    s58.settings();
    return 0;
}

The main point is that class friendship is a natural idiom that reflects some type of relationship.

) Friend member functions
Notice that you might just want several methods of Remote class to access Tv private members, thus you could declare those methods to be friend member functions to the Tv class:

class Tv
{
    friend void Remote::set_chan(Tv & t, int c);
    ...
}

But there comes two major problems:
1 Processing this command requires the compiler to be aware of the class Remote and its method set_chan(), which indicates that Remote should appear before Tv. However, class Remote contains the use of Tv objects, so class Tv should be placed ahead of class Remote. In order to resolve this conflict, a technique called forward declaration is applied:

class Tv;
class Remote { ... };
class Tv { ... };

By this way, Tv class declaration is given before Remote class, but its real definition is provided after the Remote class, which satisfies both requirements.
2 Remote class accesses Tv class methods:

void onoff(Tv & t) { t.onoff(); }

which requires Tv::onoff() to be declared before Remote class. In order to skirt this problem, only provide method prototype in Remote class, leaving the definition after the Tv class:

class Tv; // forward declaration
class Remote { ... }; // methods within it are prototypes only
class Tv { ... };
// put Remote method definitions here

To maintain the inline quality of methods, prefix the Remote methods with inline:
Here comes the code of two class declarations:

// tvfm.h -- Tv and Remote classes using a friend member
#ifndef TVFM_H_INCLUDED
#define TVFM_H_INCLUDED

class Tv;

class Remote
{
public:
    enum State{Off, On};
    enum {MinVal, MaxVal = 20};
    enum {Antenna, Cable};
    enum {TV, DVD};
private:
    int mode;
public:
    Remote(int m = Tv::TV) : mode(m) {}
    bool volup(Tv & t); // prototype only
    bool voldown(Tv & t);
    void onoff(Tv & t);
    void chanup(Tv & t);
    void chandown(Tv & t);
    void set_chan(Tv & t, int c);
    void set_mode(Tv & t);
    void set_input(Tv & t);
};

class Tv
{
public:
    friend void Remote::set_chan(Tv & t, int c);
    enum State{Off, On};
    enum {MinVal, MaxVal = 20};
    enum {Antenna, Cable};
    enum {TV, DVD};

    Tv(int s = Off, int mc = 125) : state(s), volume(5), maxchannel(mc), channel(2), mode(Cable), input(TV) {}
    void onoff() {state = (state == On) ? Off : On;}
    bool ison() {return state == On;}
    bool volup();
    bool voldown();
    void chanup();
    void chandown();
    void set_mode() {mode = (mode == Antenna) ? Cable : Antenna;}
    void set_input() {input = (input == TV) ? DVD : TV;}
    void settings() const;
private:
    int state;
    int volume;
    int maxchannel;
    int channel;
    int mode;
    int input;
};

inline bool Remote::volup(Tv & t) {return t.volup();}
inline bool Remote::voldwn(Tv & t) {return t.voldown();}
inline void Remote::onoff(Tv & t) {t.onoff();}
inline void Remote::chanup(Tv & t) {t.chanup();}
inline void Remote::chandown(Tv & t) {t.chandown();}
inline void Remote::set_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
inline void Remote::set_chan(Tv & t) {t.channel = c;}

#endif // TVFM_H_INCLUDED

The difference with the previous file is that only one method Remote::set_chan(Tv & t, int c) is friend method to the class Tv, because only this method in the Remote class accesses private members of the Tv class. Recall two techniques used in this process of using friend member functions, forward declaration of class, and only-prototype of methods which leaves method definition later with keyword inline.

) Other friendly relationships
1 Two classes might interact with each other, meaning that both class contains methods that could access the private members of the other class. In this way, you could make both classes friend to each other, or make the specific methods friend to the class it accesses.
2 Some cases require a method to be able to access two class's private members. You could make the method friend to the two required classes, then it could access the private sector of the two classes respectively and interact with them.


15.2 Nested Classes

) The class declared within another class is called a nested class, which defines a type that is known just locally to the class that contains the nested class declaration. The main purposes to use a nested class is to avoid name conflicts and assist the implementation.
) Scope of nested classes
Nested classes could be declared in the public, private or protected part of a class. The following chart illustrates the scope difference between choices:

Where Available to nesting class Available to classes derived from the nesting class Available to the outside world
private section yes no no
protected section yes yes no
public section yes yes yes, but with class qualifier

) Access control of nested classes
General rules are followed speaking of nested classes, meaning that the nesting class could only access the public members of the nested class. Common practice here is to make all the members of the nested class public.

) Nesting in a template
You could also nest classes in a template, without posing problems to the template

// queuetp.h -- queue template with a nested class
#ifndef QUEUETP_H_INCLUDED
#define QUEUETP_H_INCLUDED

template <class Item>
class QueueTP
{
private:
    enum {Q_SIZE = 10};
    class Node
    {
    public:
        Item item;
        Node * next;
        Node(const Item & i) : item(i), next(0) {}
    };
    Node * front;
    Node * rear;
    int items;
    const int qsize;
    QueueTP(const QueueTP & q) : qsize(0) {}
    Queue & operator=(const QueueTP & q) { return *this;}
public:
    QueueTP(int qs = Q_SIZE);
    ~QueueTP();
    bool isempty() const {return items == 0;}
    bool isfull() const {return items == qsize;}
    int queuecount() const {return items;}
    bool enqueue(const Item & item);
    bool dequeue(Item & item);
};

// QueueTP methods
template <class Item>
QueueTP<Item>::QueueTP(int qs) : qsize(qs)
{
    front = rear = 0;
    items = 0;
}

template <class Item>
QueueTP<Item>::~QueueTP()
{
    Node * temp;
    while (front != 0)
    {
        temp = front;
        front = front->next;
        delete temp;
    }
}

template <class Item>
bool QueueTP<Item>::enqueue(const Item & item)
{
    if (isfull())
        return false;
    Node * add = new Node(item);
    items++;
    if (front == 0)
        front = add;
    else
        rear->next = add;
    rear = add;
    return true;
}

template <class Item>
bool QueueTP<Item>::dequeue(const Item & item)
{
    if (front == 0)
        return false;
    item = front->item;
    items--;
    Node * temp = front;
    front = front->next;
    delete temp;
    if (items == 0)
        rear = 0;
    return true;
}

#endif // QUEUETP_H_INCLUDED

Noteworthy:
As the Node contains a member whose type is Item, the contents of Node is dependent on the type of the class template. Thus QueueTP<char>::Node and QueueTP<int>::Node share same name but does not conflict with each other.
Here comes the code that uses the QueueTP class defined above:

// nested.cpp -- using a queue that has a nested class
#include <iostream>
#include <string>
#include "queuetp.h"

int main()
{
    using std::string;
    using std::cin;
    using std::cout;

    QueueTP<string> cs(5);
    string temp;

    while (!cs.isfull())
    {
        cout << "Please enter your name. You will be served in the order of arrival.\nname: ";
        getline(cin, temp);
        cs.enqueue(temp);
    }
    cout << "The queue is full. Processing begins!\n";

    while (!cs.isempty())
    {
        cs.dequeue(temp);
        cout << "Now processing " << temp << "...\n";
    }
    return 0;
}

15.5 Type Cast Operators

) 1 dynamic_cast
The operator had syntax like this:

dynamic_cast<type-name> (expression)

The purpose of this is to allow upcasts within a class hierarchy and disallow other casts. For example:

A a;
B b;
a = dynamic_cast<A> (b);

Only when B derives from A could the cast be valid, letting a become the upcast version of b, otherwise a null pointer
) 2 const_cast
The operator had syntax like this:

const_cast<type-name> (expression)

The cast is only valid when both sides are the same type, negligent of whether volatile or const qualifiers they have. The main purpose is to switch between const and non-const versions safely.
But although it could switch between const and non-const, the compiler would prevent you from trying to alter const data with this trick:

// constcast.cpp -- using const_cast<>
#include <iostream>
using std::cout;
using std::endl;
void change(const int * pt, int n);

int main()
{
    int pop1 = 38383;
    const int pop2 = 2000;
    cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;
    change(&pop1, -103);
    change(&pop2, -103);
    cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;
    return 0;
}

void change(const int * pt, int n)
{
    int * pc;

    pc = const_cast<int *> (pt);
    *pt += n;
}

) 3 static_cast
The cast has similar syntax:

static_cast<type_name> (expression)

It is valid only if type_name could be converted implicitly to the type of expression, or vice versa. Otherwise, the cast is an error.
This might be the most useful type cast.

posted @ 2018-11-15 22:03  Gabriel_Ham  阅读(162)  评论(0编辑  收藏  举报