C++PrimerPlus6-15-友元、异常和其他

第15章 友元、异常和其他

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

15.1 友元

可以将类作为友元,友元类的所有方法都可以访问原始类的私有成员和保护成员。也可以只将特定的成员函数指定为另一个类的友元。

15.1.1 友元类

例如电视机Tv和遥控器Remote,既不适用is-a也不适用has-a,但遥控器可以改变电视的状态,应将Remote类作为Tv类的一个友元。
友元声明可以位于公有、私有、保护部分,所在的位置无关紧要。

class Tv
{
public:
  friend class Remote; // Remote can access Tv private parts
};

class Remote
{
public:
  bool volup(Tv& t) {return v.volup();}
}

15.1.2 友元成员函数

class Tv; // forward declaration前向声明,避免Remote不知道Tv类。
class Remote {...};
class Tv
{
  friend void Remote::set_chan(Tv& t, int c);
};

15.1.3 其他友元关系

可以互为友元类,互相影响。

15.1.4 共同的友元

函数需要访问两个类的私有数据时,可以同时是这两个类的友元。

15.2 嵌套类

可以将类声明放在另一个类中,在另一个类中的声明被称为嵌套类(nested class)。

class Queue
{
  // class scope definitions
  // 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) {}
  };
};

bool Queue::enqueue(const Item& item)
{
  if (isFull()) return false;
  Node* add = new Node(item); // create, initialize node
  ...
}

15.2.1 嵌套类和访问权限

  • 作用域:
    • 嵌套类在另一个类的私有部分声明:只有这个类知道他。
    • 在保护部分声明:对类可见,外部不可见。
    • 在公有部分声明:都可见。
  • 访问控制:对嵌套类访问权的控制规则与常规类相同。

15.2.2 模板中的嵌套

模板类包含嵌套类不会带来问题。

15.3 异常

15.3.1 调用abort()

cstdlib或stdlib.h中的abort(),终止程序。

double hmean(double a, double b)
{
  if (a == -b)
  {
    std::abort();
  }
  return 2.0 * a * b / (a + b);
}

15.3.2 返回错误码

或使用函数的返回值来指出问题,istream能告诉是否成功:

bool hmean(double a, double b, double* ans)
{
  if (a == -b)
  {
    *ans = DBL_MAX;
    return false;
  }
  else
  {
    *ans = 2.0 * a * b / (a + b);
    return true;
  }
}

if (hmean(x, y, &z)) ...;

15.3.3 异常机制

异常机制的组成部分:

  1. 引发异常
  2. 使用处理程序捕获异常
  3. 使用try块
double hmean(double a, double b)
{
  if (a == -b)
    throw "123456";
  return 2.0 * a * b / (a + b);
}

try
{
  z = hmean(x, y)
}
catch (const char* s)
{
  std::cout << s << std::endl;
}

15.3.4 将对象用作异常类型

通常,引发异常的函数将传递一个对象。可以使用不同的异常类型,来区分不同的函数在不同情况下引发的异常。另外对象还可以携带信息。

class bad_hmean
{
private:
  double v1;
  double v2;
public:
  bad_hmean(double a=0, double b=0) : v1(a), v2(b) {}
  void mesg();
};

inline void bad_hmean::mesg()
{
  std::cout << "123456";
}

class bad_gmean {}; // ...
double hmean(double a, double b)
{
  if (a == -b) throw bad_hmean(a, b);
  return 66.6;
}

double gmean(double a, double b)
{
  if (a < 0 || b < 0) throw bad_gmean(a, b);
  return 77.7;
}

int main()
{
  while (cin >> x >> y)
  {
    try
    {
      z = hmean(x, y);
      a = gmean(x, y);
    }
    catch (bad_hmean& bg)
    {
      bg.mesg();
      continue;
    }
    catch (bad_gmean& hg)
    {
      hg.mesg();
      continue;
    }
  }
}

15.3.5 异常规范和C++11

  • C++98有,但是C++11废弃了的功能:异常规范:double marn(double) throw(bad_thing);提示可能要用try
  • C++11新增但不建议用的noexcept关键字指出函数不会引发异常:double marn(double) noexcept;

15.3.6 栈解退

栈解退unwinding the stack:函数由于出现异常而终止,程序将释放栈中的内存,直到找到一个try块中的返回地址。随后控制权转到块尾的异常处理程序。
P506

15.3.7 其他异常特性

15.3.8 exception类

exception头文件定义了exception类,代码可以引发exception异常,也可以将其用作基类。

#include <exception>
class bad_hmean : public std::exception
{
public:
  const char* waht() {return "666";}
}

try{
  ...
}
catch(std::exception& e){
  cout << e.what() << endl;
}

异常类型:

  • stdexcept异常类
    • logic_error:逻辑错误
      • domain_error:定义域
      • invalid_argument:函数传递了意料外的值
      • length_error:没有足够空间
      • out_of_bounds:索引错误
    • runtime_error:运行期间发生但难以预计和防范的错误
      • range_error:计算结果不在函数允许的范围内,但没有上溢或下溢
      • overflow_error:整型和浮点型上溢错误(计算结果超过了某种类型能表示的最大数量级)
      • underflow_error:浮点数下溢错误(计算结果小于浮点类型能表示的最小非零值)
  • bad_alloc异常和new:对于使用new导致的内存分配问题,让new引发bad_alloc异常。头文件new
  • 空指针和new:有一种在失败时返回空指针的new:int* pa = new (std::nothrow) int[500];
#include <iostream>
#include <new>
#include <cstdlib> // for exit(), EXIT_FAILURE

struct Big
{
  double stuff[20000];
}

int main()
{
  Big* pb;
  try
  {
    cout << "Trying to get a big block of memory:\n";
    pb = new Big[10000]; // 1,600,000,000 bytes
  }
  catch (bad_alloc& ba)
  {
    cout << ba.what() << endl;
    exit(EXIT_FAILURE);
  }
}

15.3.9 异常、类和继承

互相关联:

  1. 可以像标准C++库那样,从一个异常类派生出另一个
  2. 可以在类定义中嵌套异常类声明来组合异常
  3. 这种嵌套声明本身可被继承,还可用作基类

15.3.10 异常何时会迷失方向

有两种情况会导致引发异常后出现问题:

  1. 意外异常:在带异常规范的函数中引发,但没有与规范列表中的某种异常匹配
  2. 未捕获异常:异常不是在函数中引发(或函数没有异常规范),没有被捕获时(没有try块或没有匹配的catch块)。

15.3.11 有关异常的注意事项

应在设计程序时就加入异常处理功能,而不是以后再添加。

15.4 RTTI

RTTI运行阶段类型识别(Runtime Type Identification)。

15.4.1 RTTI的用途

例如基类指针,判断到底是指向哪一个类的对象。

15.4.2 RTTI的工作原理

3个支持RTTI的元素:

  1. dynamic_cast:使用一个指向基类的指针来生成一个指向派生类的指针,否则返回空指针。
  2. typeid:返回一个指出对象的类型的值。
  3. type_info:存储了有关特定类型的信息。

RTTI只适用于包含虚函数的类。

dynamic_cast能够回答“是否可以安全的将对象的地址赋给特定类型的指针”:

Superb* pm = dynamic_cast<Superb*>(pg);

typeid能够确定两个对象是否为同种类型,typeid返回一个对type_info对象的引用(头文件typeinfo)

typeid(Magnificent) == typeid(*pg);
typeid(*pg).name(); // 通常是类的名称

考虑使用虚函数和dynamic_cast来代替typeid,更简洁可读。

15.5 类型转换运算符

C语言的类型转换不够规范,看不出目的。C++增加了更严格的类型转换:

  • dynamic_cast:使得能够在类层次中进行向上转换
  • const_cast:使指向const值的指针的值可以修改
  • static_cast:这个进行向下转换是合法的
  • reinterpret_cast

15.6 总结

  • 友元使得能够为类开发更灵活的接口。类可以将其他函数、其他类、其他类的成员函数作为友元。
  • 嵌套类是在其他类中声明的类。
  • C++异常机制。try…catch…
  • RTTI运行阶段类型信息。dynamic_cast, typeid, type_info
  • dynamic_cast, static_cast, const_cast, reinterpret_cast

15.7 复习题

15.8 编程练习


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 cdd@ahucd.cn

×

喜欢就点赞,疼爱就打赏

B站 cdd的庇护之地 github itch