C++中的运行时类型检查
简介
经常有人问到:“我怎样才能在运行时确定或检查C++中对象的类型呢?”,下面通过一个简单问题来作一演示。
以下程序会在第一次调用CFoo::AnimalSays 时显示“Bark!”,而第二次调用时显示“Miaou”。
class Animal {/*...*/};
class Dog : public Animal {/*...*/};
class Cat : public Animal {/*...*/};
class CFoo
{
public:
void AnimalSays(Animal*) {/*...*/}
};
int main(int argc, char* argv[])
{
Dog rex;
Cat kitty;
CFoo foo;
foo.AnimalSays(&rex);
foo.AnimalSays(&kitty);
return 0;
}
也就是说,你需要用某种方法在运行时来确定函数 CFoo::AnimalSays接受的参数,到底是指向Dog类型对象的指针,还是指向Cat类型对象的指针。
第一次尝试
我们的第一个想法是添加一个成员变量,其存储了类型的相关信息。
#include <iostream>
class Animal
{
public:
enum AnimalType {TypeDog, TypeCat};
};
class Dog : public Animal
{
public:
Dog() : m_type(TypeDog) {}
const AnimalType m_type;
};
class Cat : public Animal
{
public:
Cat() : m_type(TypeCat) {}
const AnimalType m_type;
};
class CFoo
{
public:
void AnimalSays(Animal*);
};
int main(int argc, char* argv[])
{
Dog rex;
Cat kitty;
CFoo foo;
foo.AnimalSays(&rex);
foo.AnimalSays(&kitty);
return 0;
}
void CFoo::AnimalSays(Animal* pAnimal)
{
if(((Dog*)pAnimal)->m_type == Animal::TypeDog)
std::cout << "Bark! ";
else if(((Cat*)pAnimal)->m_type == Animal::TypeCat)
std::cout << "Miaou! ";
}
现在,回过头看一下 CFoo::AnimalSays函数的实现部分,试想我们如果不只有两种动物类型,而是50多种呢(也就是说,从Animal的派生类)?这样一来,不但代码非常难看,还很难保证不出错,也难于阅读、修改、维护,这可不是一个好的解决方案。
另一个好点的方法
在静态成员变量中保存类名,通过虚拟函数来访问。
#include <iostream>
#include <string>
class Animal
{
public:
virtual bool IsOfType(const std::string&) = 0;
};
class Dog : public Animal
{
static const string m_class_name;
public:
virtual bool IsOfType(const std::string&);
};
class Cat : public Animal
{
static const string m_class_name;
public:
virtual bool IsOfType(const std::string&);
};
class CFoo
{
public:
void AnimalSays(Animal*);
};
int main(int argc, char* argv[])
{
Dog rex;
Cat kitty;
CFoo foo;
foo.AnimalSays(&rex);
foo.AnimalSays(&kitty);
return 0;
}
const string Dog::m_class_name = "Dog";
const string Cat::m_class_name = "Cat";
bool Dog::IsOfType(const std::string& class_name)
{
return (class_name == m_class_name);
}
bool Cat::IsOfType(const std::string& class_name)
{
return (class_name == m_class_name);
}
void CFoo::AnimalSays(Animal* pAnimal)
{
if(pAnimal->IsOfType("Dog"))
std::cout << "Bark! ";
else if(pAnimal->IsOfType("Cat"))
std::cout << "Miaou! ";
}
CFoo::AnimalSays现在看上去干净多了,比前一种方法也易于阅读,但还能添加两个宏来进一步简化代码:
#include <iostream>
#include <string>
#define DECLARE_RUNTIME_CLASS /
private: static const std::string m_class_name; /
public: virtual bool IsOfType(const std::string&);
#define IMPLEMENT_RUNTIME_CLASS(class_name) /
const std::string class_name##::m_class_name = #class_name; /
bool class_name##::IsOfType(const std::string& name) /
{return (name == m_class_name);}
// ...
这样一来,以后就能添加更小的代码块了。
// ...
class Animal
{
public:
virtual bool IsOfType(const std::string&) = 0;
};
class Dog : public Animal
{
DECLARE_RUNTIME_CLASS
};
class Cat : public Animal
{
DECLARE_RUNTIME_CLASS
};
class CFoo
{
public:
void AnimalSays(Animal*);
};
int main(int argc, char* argv[])
{
Dog rex;
Cat kitty;
CFoo foo;
foo.AnimalSays(&rex);
foo.AnimalSays(&kitty);
return 0;
}
IMPLEMENT_RUNTIME_CLASS(Dog)
IMPLEMENT_RUNTIME_CLASS(Cat)
void CFoo::AnimalSays(Animal* pAnimal)
{
if(pAnimal->IsOfType("Dog"))
std::cout << "Bark! ";
else if(pAnimal->IsOfType("Cat"))
std::cout << "Miaou! ";
}
现在,已经非常接近MFC的解决方案了。
如果使用MFC,这种问题简直是“小菜一碟”。大多数MFC类派生自CObject,而CObject连同一些新的MFC宏都添加了对运行时类型信息的支持,解决上述问题的MFC办法就是将你的类置于MFC继承体系之中,并使用相对应的宏,如下所示:
#include <afx.h>
class Animal : public CObject
{
DECLARE_DYNAMIC(Animal)
};
class Dog : public Animal
{
DECLARE_DYNAMIC(Dog)
};
class Cat : public Animal
{
DECLARE_DYNAMIC(Cat)
};
class CFoo
{
public:
void AnimalSays(Animal*);
};
int main(int argc, char* argv[])
{
Dog rex;
Cat kitty;
CFoo foo;
foo.AnimalSays(&rex);
foo.AnimalSays(&kitty);
return 0;
}
IMPLEMENT_DYNAMIC(Animal, CObject)
IMPLEMENT_DYNAMIC(Dog, Animal)
IMPLEMENT_DYNAMIC(Cat, Animal)
void CFoo::AnimalSays(Animal* pAnimal)
{
if(pAnimal->IsKindOf(RUNTIME_CLASS(Dog)))
printf("Bark! ");
else if(pAnimal->IsKindOf(RUNTIME_CLASS(Cat)))
printf("Miaou! ");
}
另一种方法:RTTI
C++内置的运行时类型检查机制为RTTI(Run-Time Type Information),RTTI允许使用两个操作符:typeid与dynamic_cast。用RTTI解决上述问题的第一种方法是使用typeid,它返回一个对type_info对象的引用,其保存了传递进来的对象类型信息。
#include <iostream>
class Animal {public: virtual ~Animal(){};};
class Dog : public Animal{};
class Cat : public Animal{};
class CFoo
{
public:
void AnimalSays(Animal*);
};
int main(int argc, char* argv[])
{
Dog rex;
Cat kitty;
CFoo foo;
foo.AnimalSays(&rex);
foo.AnimalSays(&kitty);
return 0;
}
void CFoo::AnimalSays(Animal* pAnimal)
{
const type_info& ti = typeid(*pAnimal);
if(ti == typeid(Dog))
std::cout << "Bark! ";
else if(ti == typeid(Cat))
std::cout << "Miaou! ";
}
第二种方法是使用dynamic_cast,如果你传递给它一个所不期望类型的指针,它将返回0,程序如下面这样:
void CFoo::AnimalSays(Animal* pAnimal)
{
if(dynamic_cast<Dog*>(pAnimal))
std::cout << "Bark! ";
else if(dynamic_cast<Cat*>(pAnimal))
std::cout << "Miaou! ";
}
注意,类Animal至少必须有一个虚拟函数。为什么?因为如果你的类不是“多种类型”的,RTTI机制不会正常工作。另外,RTTI需从“项目设置——属性”中打开,默认是关闭的。译者注:Visual C++ 2008 SP1中在“项目属性——配置属性——C/C++——语言”下,默认是打开的。
最后,真的需要它吗?
一个设计良好、用于多态的类,是无须担心运行时类型检查的,在本文这个特例中,可在抽象基类(Animal)中放置一个纯虚函数ISay,并为每个派生者添加一些具体的实现。
#include <iostream>
class Animal
{
public:
//纯虚函数
virtual void ISay() = 0;
};
class Dog : public Animal
{
public:
//Dog的特定实现
virtual void ISay() {std::cout << "Bark! ";}
};
class Cat : public Animal
{
public:
//Cat的特定实现
virtual void ISay() {std::cout << "Miaou! ";}
};
class CFoo
{
public:
void AnimalSays(Animal*);
};
int main(int argc, char* argv[])
{
Dog rex;
Cat kitty;
CFoo foo;
foo.AnimalSays(&rex);
foo.AnimalSays(&kitty);
return 0;
}
void CFoo::AnimalSays(Animal* pAnimal)
{
pAnimal->ISay();
}
看一下CFoo::AnimalSays函数,它非常清爽、简洁,无需运行时类型检查。
结论
既可用RTTI,也可用像MFC机制的方法来实现运行时类型检查,但恐怕也没什么必要。