C++中的运行时类型检查

来源:互联网 发布:蓄水池算法 面试 编辑:程序博客网 时间:2024/05/01 20:58

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机制的方法来实现运行时类型检查,但恐怕也没什么必要。

 

原创粉丝点击