-cpp友元异常和其他

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了-cpp友元异常和其他相关的知识,希望对你有一定的参考价值。

本章内容包括:

  • 友元类。
  • 友元类方法。
  • 嵌套类。
  • 引发异常、try块和catch块。
  • 异常类。
  • 运行阶段类型识别(RTTI)。
  • dynamic_cast和typeid。
  • static_cast、const_cast和reiterpret_cast。

一、友元

前面我们将友元函数用于类的扩展接口中,类并非只能拥有友元函数,也可以将类作为友元。在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员。另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元。哪些函数、成员函数或类为友元是由类定义的,而不能从外部强加友情。因此,尽管友元被授予从外部访问类的私有部分的权限,但它们并不与面向对象的编程思想相悖;相反,它们提高了公有接口的灵活性。

友元类:

什么时候希望一个类成为另一个类的友元呢?我们来看一个例子。假定需要编写一个模拟电视机和遥控器的简单程序。决定定义一个Tv类和一个Remote类,来分别表示电视机和遥控器。很明显,这两个类之间应当存在某种关系,但是什么样的关系呢?遥控器并非电视机,反之亦然,所以公有继承的is-a关系并不适用。遥控器也非电视机的一部分,反之亦然,因此包含或私有继承和保护继承的has-a关系也不适用。事实上,遥控器可以改变电视机的状态,这表明应将Romote类作为Tv类的一个友元。

// tv.h -- Tv and Remote classes
#ifndef TV_H_
#define TV_H_
class Tv 
public:
    friend class Remote;   // Remote can access Tv private parts
    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() const 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; // display all settings
private:
    int state;             // on or off
    int volume;            // assumed to be digitized
    int maxchannel;        // maximum number of channels
    int channel;           // current channel setting
    int mode;              // broadcast or cable
    int input;             // TV or DVD
;

class Remote 
private:
    int mode;              // controls TV or DVD
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.cpp -- methods for the Tv class (Remote methods are inline)
#include <iostream>
#include "tv.h"
bool Tv::volup() 
	if (volume < MaxVal) 
		volume++;
		return true;
	 else
		return false;

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

void Tv::chanup() 
	if (channel < maxchannel)
		channel++;
	else
		channel = 1;

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

这个示例的主要目的在于表明,类友元是一种自然用语,用于表示一些关系。如果不使用某些形式的友元关系,则必须将Tv类的私有部分设置为公有的,或者创建一个笨拙的、大型类来包含电视机和遥控器。这种解决方法无法反应这样的事实,即同一个遥控器可用于多台电视机。

让Remote类的set_chan()函数成为Tv类的友元的方法是,在Tv类声明中将其声明为友元:

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

然而,要使编译器能够处理这条语句,它必须知道Remote的定义。否则,它无法知道Remote是一个类,而set_chan是这个类的方法。这意味着应将Remote的定义放到Tv的定义前面。Remote的方法提到了Tv对象,而这意味着Tv定义应当位于Remote定义之前。避开这种循环依赖的方法是,使用前向声明(forward declaration)。为此,需要在Remote定义的前面插入下面的语句:

class Tv;// forward declaration
正确的排列:
class Tv;// 前向声明
class Remote ...;
class Tv ...;
————————————————————————
不能像下面这样排列:
class Remote;// 前向声明
class Tv ...;
class Remote ...;

不能用以上的第二种排列方式,原因在于:在编译器在Tv类的声明中看到Remote的一个方法被声明为Tv类的友元之前,应该先看到Remote类的声明和set_chan( )方法的声明。

还有一个麻烦。程序清单中的Remote声明包含了内联代码,例如:

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

由于这将调用Tv的一个方法,所以编译器此时必须已经看到了Tv类的声明,这样才能知道Tv有哪些方法,但正如看到的,该声明位于Remote声明的后面。这种问题的解决方法是,使Remote声明中只包含方法声明,并将实际的定义放在Tv类之后。这样,排列顺序将如下:

class Tv;// 前向声明
class Remote ...;// 这里只定义Tv要使用的方法的原型
class Tv ...;
把Remote的实现方法放到这里

顺便说一句,让整个Remote类成为友元并不需要前向声明,因为友元语句本身已经指出Remote是一个类。 

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

class Tv;                       // forward declaration

class Remote 
public:
    enum StateOff, On;
    enum MinVal,MaxVal = 20;
    enum Antenna, Cable;
    enum TV, DVD;
private:
    int mode;
public:
    Remote(int m = 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_mode(Tv & t);
    void set_input(Tv & t);
    void set_chan(Tv & t, int c);
;

class Tv 
public:
    friend void Remote::set_chan(Tv & t, int c);
    enum StateOff, 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() const 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;
;

// Remote methods as inline functions
inline bool Remote::volup(Tv & t)  return t.volup();
inline bool Remote::voldown(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, int c) t.channel = c; 
#endif

如果在tv.cpp和use_tv.cpp中包含tvfm.h而不是tv.h,程序的行为与前一个程序相同,区别在于,只有一个Remote方法是Tv类的友元,而在原来的版本中,所有的Remote方法都是Tv类的友元。

需要使用友元的另一种情况是,函数需要访问两个类的私有数据。从逻辑上看,这样的函数应是每个类的成员函数,但这是不可能的。它可以是一个类的成员,同时是另一个类的友元,但有时将函数作为两个类的友元更合理。例如,假定有一个Probe类和一个Analyzer类,前者表示某种可编程的测量设备,后者表示某种可编程的分析设备。这两个类都有内部时钟,且希望它们能够同步,则应该包含下述代码行:

class Analyzer;// forward declaration
class Probe 
    friend void sync(Analyzer& a, const Probe& p);// sync a to p
    friend void sync(Probe& p, const Analyzer& a);// sync p to a
    ...
;
class Analyzer 
    friend void sync(Analyzer& a, const Probe& p);// sync a to p
    friend void sync(Probe& p, const Analyzer& a);// sync p to a
    ...
;
// define the friend functions
inline void sync(Analyzer& a, const Probe& p) ...
inline void sync(Probe& p, const Analyzer& a) ...

前向声明使编译器看到Probe类声明中的友元声明时,知道Analyzer是一种类型。

二、嵌套类

对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突。

下面Queue类示例嵌套了结构定义,从而实现了一种变相的嵌套类:

class Queue 
private:
    struct Node  Item item; struct Node* next;;
    ...

由于结构是一种其成员在默认情况下为公有的类,所以Node实际上是一个嵌套类,但该定义并没有充分利用类的功能。具体地说,它没有显式构造函数。

enqueue()方法创建了结构:

bool Queue::enqueue(const Item& item) 
    if (isfull())
        return false;
    Node* add = new Node;// create node
// on failure, new throws std::bad_alloc exception
    add->item = item;
    add->next = NULL;
    ...

上述代码创建Node后,显式地给Node成员赋值,这种工作更适合由构造函数来完成。

知道应在什么地方以及如何使用构造函数后,便可以提供一个适当的构造函数定义:

class Queue 
// class scope definition
    // Node is a nested class definition local to this class
    class Node 
    public:
        Item item;
        Node* next;
        Node(const Item& i) : item(i), next(0) 
    ;
    ...
;

该构造函数将节点的item成员初始化为i,并将next指针设置为0,这是使用C++编写空值指针的方法之一(使用NULL时,必须包含一个定义NULL的头文件;如果你使用的编译器支持C++11,可使用nullptr)。由于使用Queue类创建的所有节点的next的初始值都被设置为空指针,因此这个类只需要该构造函数。

接下来,需要使用构造函数重新编写enqueue( ):

bool Queue::enqueue(const Item& item) 
    if (isfull())
        return false;
    Node* add = new Node(item);// create, initialize node
// on failure, new throws std::bad_alloc exception
    ...

这个例子在类声明中定义了构造函数。假设想在方法文件中定义构造函数,则定义必须指出Node类是在Queue类中定义的。这是通过使用两次作用域解析运算符来完成的:

Queue::Node::Node(const Item& i) : item(i), next(0) ...

➊作用域

如果嵌套类是在另一个类的私有部分声明的,则只有后者知道它。因此,Queue成员可以使用Node对象和指向Node对象的指针。对于从Queue派生而来的类,Node也是不可见的,因为派生类不能直接访问基类的私有部分。

如果嵌套类是在另一个类的保护部分声明的,则它对于后者来说是可见的,但是对于外部世界则是不可见的。然而,在这种情况中,派生类将知道嵌套类,并可以直接创建这种类型的对象。

如果嵌套类是在另一个类的公有部分声明的,则允许后者、后者的派生类以及外部世界使用它,因为它是公有的。然而,由于嵌套类的作用域为包含它的类,因此在外部世界使用它时,必须使用类限定符。例如,假设有下面的声明:

class Team 
public:
    class Coach ...;
    ...
;

 现在假定有一个失业的教练,他不属于任何球队。要在Team类的外面创建Coach对象,可以这样做:

Team::Coach forhire;// create a Coach object outside the Team class

嵌套结构和枚举的作用域与此相同。其实,很多程序员都使用公有枚举来提供可供客户程序员使用的类常数。 

嵌套类、结构和枚举的作用域特征,如下表:

➋访问控制

类可见后,起决定作用的将是访问控制。对嵌套类访问权的控制规则与对常规类相同。在Queue类声明中声明Node类并没有赋予Queue类任何对Node类的访问特权,也没有赋予Node类任何对Queue类的访问特权。因此,Queue类对象只能显示地访问Node对象的公有成员。由于这个原因,在Queue示例中,Node类的所有成员都被声明为公有的。这样有悖于应将数据成员声明为私有的这一惯例,但Node类是Queue类内部实现的一项特性,对外部世界是不可见的。这是因为Node类是在Queue类的私有部分声明的。所以,虽然Queue的方法可直接访问Node的成员,但使用Queue类的客户不能这样做。

总之,类声明的位置决定了类的作用域或可见性。类可见后,访问控制规则(公有、保护、私有、友元)将决定程序对嵌套类成员的访问权限。

未完待续。。。

以上是关于-cpp友元异常和其他的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 友元

cpp►运算符重载与友元friend

友元函数返回 (void*):无法在 .cpp 文件中执行实现

友元函数

C++ Primer Plus学习:第十五章

如何实现一个类函数声明为另一个类的友元,并且两个类在不同的文件中, 举例如下: