《C++ Primer》第19章 特殊工具与技术
19.2节 运行时类型识别 习题答案
练习19.3:已知存在如下的类继承体系,其中每个类分别定义了一个公有的默认构造函数和一个虚析构函数:
class A { /* ... */ };
class B: public A { /* ... */ };
class C: public B { /* ... */ };
class D: public B, public A { /* ... */ };
下面的哪个dynamic_cast将失败?
(a) A *pa = new C;
B *pb = dynamic_cast(pa);
(b) B *pb = new B;
C *pc = dynamic_cast(pb);
(c) A *pa = new D;
B *pb = dynamic_cast(pa);
【出题思路】
根据dynamic_cast运算符定义中的实际绑定对象类型和目标对象类型之间的要求关系,判断dynamic_cast能否正常转换目标。
【解答】
使用dynamic_cast操作符时,如果运行时实际绑定到引用或指针的对象不是目标类型的对象(或其公有派生类的对象),则dynamic_cast失败。
(b)dynamic_cast转换失败。因为目标类型是C,但pb实际指向的不是C类对象,而是一个B类(C的基类)对象。
而(c)会编译失败,因为A是D的一个二义基类。
注意,要使用RTTI,一般需要在编译器中设置相应编译选项。例如,在MicrosoftVisual C++ .NET 2003中,在project菜单中选择properties菜单项,在configuration properties_C/C++->Language中打开RTTI选项。
练习19.4:使用上一个练习定义的类改写下面的代码,将表达式*pa转换成类型C&:
if(C *pc = dynamic_cast(pa))
//使用C的成员
}else {
//使用A的成员
}
【出题思路】
因引用类型和指针类型的dynamic_cast在表示错误发生的方式上有不同之处,通过改写程序,练习不同的异常发生方式。
【解答】
使用dynamic_cast将基类引用转换为派生类引用时,如果转换失败,会抛出一个std::bad_cast异常,因此可以这样重写上述代码:
#include
#include
using namespace std;
class A {
public:
A() { }
virtual ~A() { }
};
class B: public A {
public:
B() { }
virtual ~B() { }
};
class C: public B {
public:
C() { }
virtual ~C() { }
};
int main()
{
A *pa = new A;
try {
C &c = dynamic_cast(*pa);
}
catch (std::bad_cast &bc)
{
cout << "dynamic_cast failed=====" << endl;
}
return 0;
}
运行结果 :dynamic_cast failed=====
练习19.5:在什么情况下你应该使用dynamic_cast替代虚函数?
【出题思路】
具体情况下,dynamic_cast的使用选择。
【解答】
如果我们需要在派生类中增加新的成员函数f,但又无法取得基类的源代码,因而无法在基类中增加相应的虚函数,这时,可以在派生类中增加非虚成员函数。但这样一来,就无法用基类指针调用函数f。如果在程序中需要通过基类指针(如使用该继承层次的某个类中所包含的指向基类对象的指针数据成员p)来调用f,则必须使用dynamic_cast将p转换为只想派生类的指针,才能调用f。也就是说,如果无法为基类增加虚函数,就可以使用dynamic_cast代替虚函数。
练习19.6:编写一条表达式将Query_base指针动态转换为AndQuery指针(参见15.9.1节,第564页)。分别使用AndQuery的对象以及其他类型的对象测试转换是否有效。打印一条表示类型转换是否成功的信息,确保实际输出的结果与期望的一致。
【出题思路】
使用dynamic_cast动态转换类型的练习,并测试结果。
【解答】
假设指针qb的类型为Query_base*,则表达式dynamic_cast
#include
using namespace std;
class Query_base
{
public:
Query_base() { }
virtual ~Query_base() { }
// ...
};
class BinaryQuery: public Query_base
{
public:
BinaryQuery() { }
virtual ~BinaryQuery() { }
// ...
};
class AndQuery: public BinaryQuery
{
public:
AndQuery() { }
virtual ~AndQuery() { }
// ...
};
int main()
{
Query_base *qb = new Query_base;
if(dynamic_cast(qb) != nullptr)
{
cout << "success.=========" << endl;
}
else
{
cout << "failure.=========" << endl;
}
return 0;
}
运行结果:failure.=========
练习19.7:编写与上一个练习类似的转换,这一次将Query_base对象转换为AndQuery的引用。重复上面的测试过程,确保转换能正常工作。
【出题思路】
练习使用dynamic_cast动态转换类型。
【解答】
假设指针qb的类型为Query_base*,则表达式dynamic_cast
#include
#include
using namespace std;
class Query_base
{
public:
Query_base() { }
virtual ~Query_base() { }
// ...
};
class BinaryQuery: public Query_base
{
public:
BinaryQuery() { }
virtual ~BinaryQuery() { }
// ...
};
class AndQuery: public BinaryQuery
{
public:
AndQuery() { }
virtual ~AndQuery() { }
// ...
};
int main()
{
Query_base *qb = new Query_base;
try
{
dynamic_cast(qb);
cout << "success.=========" << endl;
}
catch(bad_cast &bc)
{
cout << "failure.=========" << endl;
}
return 0;
}
运行结果:success.=========
练习19.8:编写一条typeid表达式检查两个Query_base对象是否指向同一种类型。再检查该类型是否是AndQuery。
【出题思路】
使用typeid运算符进行表达式检查的练习。
【解答】
假设指针qb1和qb2的类型为Query_base*,则判断两个Query_base指针是否指向相同类型的typeid表达式如下:
typeid(*qb1) == typeid(*qb2)
判断该类型是否为AndQuery的typeid表达式如下:
typeid(*qb1) == typeid(AndQuery)
练习19.9:编写与本节最后一个程序类似的代码,令其打印你的编译器为一些常见类型所起的名字。如果你得到的输出结果与本书类似,尝试编写一个函数将这些字符串翻译成人们更容易读懂的形式。
【出题思路】
type_info类的使用练习。
【解答】
#include
#include
using namespace std;
class Base { };
class Derived: public Base { };
int main()
{
Base b, *pb;
pb = nullptr;
Derived d;
cout << typeid (int).name() << endl
<< typeid (unsigned).name() << endl
<< typeid (long).name() << endl
<< typeid (unsigned long).name() << endl
<< typeid (char).name() << endl
<< typeid (unsigned char).name() << endl
<< typeid (float).name() << endl
<< typeid (double).name() << endl
<< typeid (string).name() << endl
<< typeid (Base).name() << endl
<< typeid (b).name() << endl
<< typeid (pb).name() << endl
<< typeid (Derived).name() << endl
<< typeid (d).name() << endl
<< typeid (type_info).name() << endl;
return 0;
}
运行结果:
练习19.10:已知存在如下的类继承体系,其中每个类定义了一个默认公有的构造函数和一个虚析构函数。下面的语句将打印哪些类型名字?
class A { /* ... */ }
class B: public A { /* ... */ }
class C: public B { /* ... */ }
(a) A *pa = new C;
cout << typeid(pa).name() << endl;
(b) C cobj;
A& ra = cobj;
cout << typeid(&ra).name() << endl;
(c) B *px = new B;
A& ra = *px;
cout << typeid(ra).name() << endl;
【出题思路】
熟悉理解type_info类对象信息。
【解答】
typeid操作符的结果是type_info类对象,type_info类的成员函数name返回一个C风格字符串,代表type_info对象所表示的类型的名字。返回的C风格字符串的格式和值根据编译器而定。
上述语句显示的类型名分别是:
(a) class A*
因为pa是指向A类对象的指针,其类型为A*。
(b) class A*
因为ra是A类对象的引用,表达式&ra求得ra的地址,该地址的类型为A*
(c) class A
因为ra是A类对象的引用,其类型为A。