【问题标题】:Reflect a class' inheritance tree in C++?在 C++ 中反映类的继承树?
【发布时间】:2011-02-17 09:14:56
【问题描述】:

假设我在 C++ 中有以下类,我想检查它们的继承:

Vehicle

MotorcarVehicle
AircraftVehicle

BiplaneAircraftVehicle
HelicopterAircraftVehicle

我想写一个方法getClassLineage()来做以下事情:

Biplane b;
cout << b.getClassLineage() << endl; // prints "Vehicle--Aircraft--Biplane"

Helicopter h;
cout << h.getClassLineage() << endl; // prints "Vehicle--Aircraft--Helicopter"

Motorcar m;
cout << m.getClassLineage() << endl; // prints "Vehicle--Motorcar"

似乎应该有一种简单的递归方式来做到这一点,只需在超类中编写一次,而无需在每个派生类中复制一个基本相同的方法。

假设我们愿意声明(伪代码)Helicopter.className = "Helicopter"typedef Aircraft baseclass 在每个派生类中,但尽量避免复制和粘贴 getClassLineage()

有没有优雅的写法?

(感谢您的意见!)

【问题讨论】:

标签: c++ inheritance


【解决方案1】:

解决方案 1

如果你对修饰名没问题,那么你可以写一个免费的函数模板:

struct Vehicle {};
struct Aircraft : Vehicle { typedef Vehicle super; };
struct Helicopter : Aircraft { typedef Aircraft super; };
 
template<typename T>
string getClassLineage()
{
   static string lineage = string(typeid(T).name()) +" - " + getClassLineage<typename T::super>();
   return lineage;
}
template<>
string getClassLineage<Vehicle>()
{
   static string lineage = string(typeid(Vehicle).name());
   return lineage;
}
 
int main() {
        cout << getClassLineage<Helicopter>() << endl;
        return 0;
}

输出(修饰名称):

10直升机 - 8飞机 - 7车辆

见ideone:http://www.ideone.com/5PoJ0

如果你愿意,你可以去掉装饰。但这将是特定于编译器的! Here 是利用remove_decoration 函数剥离装饰的版本,然后输出变为:

直升机 - 飞机 - 车辆

顺便说一句,正如我所说,remove_decoration 函数的实现是特定于编译器的;此外,这可以用更正确的方式编写,因为我不知道 GCC 考虑的所有情况,而 mangling 是类名。但我希望你能明白基本的想法。


解决方案 2

如果您可以在每个派生类中重新定义函数,那么这里有一个简单的解决方案:

struct Vehicle 
{ 
   string getClassLineage() const { return "Vehicle"; } 
};
struct Aircraft : Vehicle 
{ 
   string getClassLineage() const { return Vehicle::getClassLineage()+" - Aircraft"; } 
};
struct Helicopter : Aircraft 
{ 
   string getClassLineage() const { return Aircraft::getClassLineage()+" - Helicopter "; } 
};
 
int main() {
        Helicopter heli;
        cout << heli.getClassLineage() << endl;
        return 0;
}

输出:

车辆 - 飞机 - 直升机

在 ideone 查看输出:http://www.ideone.com/Z0Tws

【讨论】:

  • Solution 1中,为每个类添加一个带有类名的静态字符串,并使用它来代替typeid().name()。更快更整洁。
  • @aaz:顺便说一句,同样的改进也可以在解决方案 2 中进行。但我希望OP本人会这样做!
  • @Nawaz,我认为 OP 特别不喜欢解决方案 2(“不复制基本相同的方法”)。不过,没有提到复制本质上相同的属性。
  • 很抱歉,如果这很无聊,但是否可以制作 Soln。如果仅在运行时知道类型,则 1 是否有效?想象一下,你有一个Vehicles 数组,其中一些具体是Vehicle,一些是Aircraft,一些是Helicopter。目标是能够遍历数组并在每个对象上调用getClassLineage()。我认识索恩。 2 可以做到这一点,但我希望避免在每个派生类中重复 getClassLineage(),似乎应该可以在每个派生类中免费继承此功能。哦,好吧。
  • @elliot42:这些解决方案都不能在您的上下文中使用。第二个可以通过使函数虚拟化来轻松调整,使调用能够被分派到最派生的类型,然后从那里开始(基本上是我发布的解决方案)
【解决方案2】:

如果您想要一种类似递归的方法,您可以使用虚函数和显式作用域函数调用来实现:

struct vehicle {
   virtual std::string lineage() const { return "vehicle"; }
};
struct aircraft : vehicle {
   typedef vehicle base;
   virtual std::string lineage() const { return base::lineage() + "--aircraft"; }
};
struct biplane : aircraft {
   typedef aircraft base;
   virtual std::string lineage() const { return base::lineage() + "--biplane"; }
};
struct nieuport17 : biplane {
   typedef biplane base;
   virtual std::string lineage() const { return base::lineage() + "--nieuport17"; }
};
int main() {
   biplane b;
   aircraft const & a = b;
   std::cout << a.lineage() << std::endl;
}

它是如何工作的?当您调用v.lineage() 因为它是一个虚函数时,动态调度将进入biplane::lineage(),因为这是对象的实际类型。在该函数内部,有对其父函数的lineage() 函数的合格调用。合格的调用不使用动态调度机制,所以调用实际上会在父级执行。基本上是这样的:

a.lineage() -- dynamic dispatch -->
---> biplane::lineage() 
     \__ airplane::lineage()
         \__ vehigcle::lineage() 
          <-- std::string("vehicle")
      <-- std::string("vehicle") + "--airplane"
  <-- std::string("vehicle--airplane") + "--biplane"
<--- std::string( "vehicle--airplane--biplane" )

【讨论】:

  • 我发现这种递归虚函数方法非常优雅。 :)
  • 一个缺点是当我希望所有子类都应该能够免费继承它时,你必须继续覆盖lineage()。但这很清楚,谢谢!
  • @eliot42:C++ 没有内置反射,这意味着无法从代码访问类名(std::typeinfo::name 是该类型的表示,但不需要,也不是,正是类名)。这意味着无论如何您都需要在每个级别提供名称。如果名称是您唯一需要更新的内容,那会稍微简单一些。我对 CRTP 做了一个简单的尝试,但在第一种方法中,您仍然需要在每个类级别声明/定义名称,并添加一个 using 声明来解决一些歧义。
【解决方案3】:

[...]但尽量避免复制和粘贴 getClassLineage()。

据我所知,这是不可能的。 C++ 本身没有反射,因此程序员必须自己完成工作。以下 C++0x 版本在 Visual Studio 2010 下工作,但我不能说对于其他编译器:

#include <string>
#include <typeinfo>
#include <iostream>

class Vehicle{
public:
        virtual std::string GetLineage(){
                return std::string(typeid(decltype(this)).name());
        }
};

class Aircraft : public Vehicle{
public:
        virtual std::string GetLineage(){
                std::string lineage = std::string(typeid(decltype(this)).name());
                lineage += " is derived from ";
                lineage += Vehicle::GetLineage();
                return lineage;
        }
};

class Biplane : public Aircraft{
public:
        virtual std::string GetLineage(){
                std::string lineage = std::string(typeid(decltype(this)).name());
                lineage += " is derived from ";
                lineage += Aircraft::GetLineage();
                return lineage;
        }
};

class Helicopter : public Aircraft{
public:
        virtual std::string GetLineage(){
                std::string lineage = std::string(typeid(decltype(this)).name());
                lineage += " is derived from ";
                lineage += Aircraft::GetLineage();
                return lineage;
        }
};

int main(){    
        Vehicle v;
        Aircraft a;
        Biplane b;
        Helicopter h;

        std::cout << v.GetLineage() << std::endl;
        std::cout << a.GetLineage() << std::endl;
        std::cout << b.GetLineage() << std::endl;
        std::cout << h.GetLineage() << std::endl;

        std::cin.get();
        return 0;
}

输出:

class Vehicle *
class Aircraft * is derived from class Vehicle *
class Biplane * is derived from class Aircraft *
class Helicopter * is derived from class Aircraft *

ideone 的输出略有不同,它去掉了星号并在名称的开头用 P 装饰了指针,但它可以工作。有趣的事实:尝试使用 typeid(decltype(*this)).name() 导致 VS2010 的编译器崩溃。

【讨论】:

  • 另外提到这是 C++0x 解决方案,不是 C++03 和 C++98。
【解决方案4】:

您需要一个静态字段来存储血统,并且每个类都会在其自己的静态字段中附加自己的血统。

如果您正在考虑使用 typeid() 或类似的东西,它更复杂但可以避免重复 getClassLineage() 方法,请记住 name 字段属性很烦人(原因超出了我的理解)不是类的真实名称,而是一个可以是该名称或任何类型的错误名称(即未定义的表示)的字符串。

如果我们使用 Python 或任何其他基于原型的编程语言,您可以轻松应用递归方法,就像您建议的那样,其中继承是通过委托实现的,因此可以遵循“继承路径”。

#include <iostream>
#include <string>

class Vehicle {
public:
  static const std::string Lineage;

  Vehicle() {}
  virtual ~Vehicle() {}

  virtual const std::string &getClassLineage()
     { return Vehicle::Lineage; }
};

class Motorcar : public Vehicle {
public:
  static const std::string Lineage;

  Motorcar() {}
  virtual ~Motorcar() {}

  virtual const std::string &getClassLineage()
     { return Motorcar::Lineage; }
};

class Helicopter : public Vehicle {
public:
  static const std::string Lineage;

  Helicopter() {}
  virtual ~Helicopter() {}

  virtual const std::string &getClassLineage()
     { return Helicopter::Lineage; }
};

class Biplane : public Vehicle {
public:
  static const std::string Lineage;

  Biplane() {}
  virtual ~Biplane() {}

  virtual const std::string &getClassLineage()
     { return Biplane::Lineage; }
};

const std::string Vehicle::Lineage = "Vehicle";
const std::string Motorcar::Lineage = "Vehicle::Motorcar";
const std::string Helicopter::Lineage = "Vehicle::Helicopter";
const std::string Biplane::Lineage = "Vehicle::Biplane";


int main()
{
    Biplane b;
    std::cout << b.getClassLineage() << std::endl; // prints "Vehicle--Aircraft--Biplane"

    Helicopter h;
    std::cout << h.getClassLineage() << std::endl; // prints "Vehicle--Aircraft--Helicopter"

    Motorcar m;
    std::cout << m.getClassLineage() << std::endl; // prints "Vehicle--Motorcar"

    return 0;
}

【讨论】:

  • 如果您只是返回 Helicopter 或 Motorcar,这很好,但返回整个血统有点脆弱 - 如果您的层次结构发生变化,所以 Biplane 现在派生自 Aircraft 怎么办?
  • @tenp,是的,这是真的,但正如我在答案顶部解释的那样,我想不出其他方法。您当然可以从中创建衍生品,但它们都会遇到同样的维护问题。
【解决方案5】:
#include <iostream>
#include <ios>
#include <iomanip>
#include <fstream>
#include <cstdio>
#include <list>
#include <sstream>

using namespace std;

static const char *strVehicle = "Vehicle";
static const char *strMotorcar = "Motorcar";
static const char *strHelicopter = "Helicopter";

class Vehicle
{
private:
  const char *ClassName;
protected:
  int Lineage;
    list<const char *> MasterList;
public:
  Vehicle(const char *name = strVehicle)
    {
        MasterList.push_back(name);
    }
  virtual ~Vehicle() {}
  virtual int getClassLineage() const
  {
    return Lineage;
  }
  string getName() const
    {
        list<const char *>::const_iterator it = MasterList.begin();
        ostringstream ss( ios_base::in | ios_base::out );
        while(it != MasterList.end())
        {
            ss << *(it++);
            if(it != MasterList.end())
                ss << " --> ";
        }
        ss << endl;
        ss << ends;
        return ss.str();
    }
};

class Motorcar : public Vehicle
{
private:
  const char *ClassName;
public:
  Motorcar(const char *name = strMotorcar)
    {
        MasterList.push_back(name);
    }
  virtual ~Motorcar() {}
  using Vehicle::getClassLineage;
  using Vehicle::getName;
};

class Helicopter : public Vehicle
{
private:
  const char *ClassName;
public:
  Helicopter(const char *name = strHelicopter)
    {
        MasterList.push_back(name);
    }
  virtual ~Helicopter() {}
  using Vehicle::getClassLineage;
  using Vehicle::getName;
};


int _tmain(int argc, _TCHAR* argv[])
{
    Helicopter h;
    Motorcar m;
    wcout << "Heli: " << h.getName().c_str() << endl;
    wcout << "Motorcar: " << m.getName().c_str() << endl;
    return 0;
}

【讨论】:

  • 愿上帝怜悯你的灵魂。
【解决方案6】:

如果使用typeid,则不需要硬编码字符串(类名)。您的问题的解决方案可能是:

#include <iostream>
#include <typeinfo>
using namespace std;

class Vehicle
{
public: 
    Vehicle();  
    string GetClassLineage(){return strName;}
protected:
    string strName;
};

Vehicle::Vehicle() : strName(typeid(*this).name())
{
    // trim "class "
    strName = strName.substr(strName.find(" ") + 1);
}

class Motorcar : public Vehicle
{
public: 
    Motorcar();
};

Motorcar::Motorcar()
{
    string strMyName(typeid(*this).name());
    strMyName = strMyName.substr(strMyName.find(" ") + 1);  

    strName += " -- ";  
    strName += strMyName;
}

int main()
{
    Motorcar motorcar;
    cout << motorcar.GetClassLineage() << endl;
    return 0;
}

输出:

Vehicle -- Motorcar

【讨论】:

  • 酷,这将完成工作,但如果我理解正确,您将不得不将Motorcar::Motorcar() 中的所有这些行复制并粘贴到所有进一步派生类的构造函数中,对吗?这似乎有点痛苦。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多