Reflect a class' inheritance tree in C++?
Say I have the following classes in C++, and I want to inspect their inheritance:
Vehicle
Motorcar
is a Vehicle
Aircraft
is a Vehicle
Biplane
is an Aircraft
is a Vehicle
Helicopter
is an Aircraft
is a Vehicle
.
I want to write a method getClassLineage()
to do the following:
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"
It seems like there should be a simple recursive way to do this by writing it once in the super-class,开发者_如何学C without duplicating an essentially identical method in every single one of the derived classes.
Assume we're willing to declare (pseudocode)Helicopter.className = "Helicopter"
and
typedef Aircraft baseclass
in each of the derived classes, but trying to avoid copying and pasting getClassLineage()
.
Is there an elegant way to write this?
(Thank you for your thoughts!)
Solution 1
IF you're okay with the decorated name, then you can write a free function template:
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;
}
Output (decorated names):
10Helicopter - 8Aircraft - 7Vehicle
See at ideone: http://www.ideone.com/5PoJ0
You can strip off the decoration if you want. But it would be compiler specific! Here is a version that makes use of remove_decoration
function to strip off the decoration, and then the output becomes :
Helicopter - Aircraft - Vehicle
By the way, as I said, the implementation of remove_decoration
function is a compiler specific; also, this can be written in more correct way, as I don't know all cases which GCC considers, while mangling the class names. But I hope, you get the basic idea.
Solution 2
If you're okay with redefining the function in each derived class, then here is a simple solution:
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;
}
Output:
Vehicle - Aircraft - Helicopter
See output at ideone: http://www.ideone.com/Z0Tws
If you want a recursive-like approach you can do it with virtual functions and explicit scoped function calls:
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;
}
How does it work? When you call v.lineage()
as it is a virtual function it the dynamic dispatch will make its way into biplane::lineage()
as that is the actual type of the object. Inside that function there is a qualified call to its parent's lineage()
function. Qualified calls do not use the dynamic dispatch mechanism, so the call will actually execute at the parents level. Basically this is what is going on:
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" )
[...]but trying to avoid copying and pasting getClassLineage().
As far as I know, that's not possible. C++ doesn't have reflection in and of itself, so the programmer has to do the work himself. The following C++0x version works under Visual Studio 2010, but I can't say for other compilers:
#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;
}
Output:
class Vehicle *
class Aircraft * is derived from class Vehicle *
class Biplane * is derived from class Aircraft *
class Helicopter * is derived from class Aircraft *
The output is slightly different at ideone, it drops the asterisk and decorates the name with a P at the beginning for pointer, but it works. Fun fact: trying to use typeid(decltype(*this)).name()
crashed VS2010's compiler for me.
You need a static field to store the lineage, and each class will have its own lineage appended in its own static field.
If you are thinking about using typeid() or something like that, which is more complex but would avoid the repetition of the getClassLineage() method, remember that the name field attribute is annoyingly (the reason for this is beyond me) not the true name of the class, but a string that can be that name or any kind of mangled name (i.e., undefined representation).
You could easily apply a recursive aproach as the one you suggest if we were using Python or any other prototype-based programming language, in which inheritance is implemented by delegation, and thus the "inheritance path" can be followed.
#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;
}
#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;
}
If using typeid you don't need to hardcode strings (class' names). Solution for your problem could be:
#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;
}
Output:
Vehicle -- Motorcar
精彩评论