1

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, 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!)

Community
  • 1
  • 1
elliot42
  • 3,694
  • 3
  • 26
  • 27

6 Answers6

10

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

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    In _Solution 1_, add a static string with the class name to each class and use that instead of `typeid().name()`. Faster and neater. – aaz Feb 17 '11 at 11:49
  • @aaz: By the way, the same improvement can be done in solution 2 as well. But I hope OP himself would do this! – Nawaz Feb 17 '11 at 12:01
  • 1
    @Nawaz, I think OP specifically doesn't like solution 2 ("without duplicating an essentially identical method"). Nothing said about duplicating an essentially identical property, though. – aaz Feb 17 '11 at 12:27
  • Sorry if this is noobish, but is it possible to make Soln. 1 work if the types are only known at runtime? Imagine you have an array of `Vehicle`s, and some of them are specifically `Vehicle`, some `Aircraft`, some `Helicopter`. The goal is to be able to iterate through the array and call `getClassLineage()` on each object. I know Soln. 2 will work to do this but I was hoping to avoid duplicating `getClassLineage()` in every derived class, it seemed like it should have been possible to inherit this functionality for free in every derived class. Oh well. – elliot42 Feb 17 '11 at 22:05
  • 1
    @elliot42: none of these solutions can be used in your context. The second can be easily adapted by making the function virtual, enabling the calls to be dispatched to the most derived type and then coming up from there (basically the solution I posted) – David Rodríguez - dribeas Feb 18 '11 at 09:13
  • As David said the problem with this arrangement is that these are all statically dispatched, not dynamically! – elliot42 Feb 28 '11 at 23:47
5

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" )
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 2
    I find this recursive virtual function approach very elegant. :) – Mephane Feb 17 '11 at 11:13
  • One downside is that you have to keep overriding `lineage()` when I was hoping all the subclasses should just be able to _inherit_ it for free. But this is pretty clear, thanks! – elliot42 Feb 17 '11 at 21:02
  • 1
    @eliot42: C++ does not have built in reflection, which means that class names are not accessible from code (the `std::typeinfo::name` is a representation of the type, but does not need to, nor is, exactly the class name). That means that you would need to provide the name at each level anyway. It would be slightly simpler if the name would be the only thing you needed to update. I have made a simple try of the CRTP, but in the first approach you would still need to declare/define the name at each class level, plus add a using declaration to resolve some ambiguity. – David Rodríguez - dribeas Feb 18 '11 at 08:57
2

[...]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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
0

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;
}
Baltasarq
  • 12,014
  • 3
  • 38
  • 57
  • This is great if you're just returning Helicopter or Motorcar, but it's a bit fragile to return the whole lineage - what if your hierarchy changes so Biplane now derives from Aircraft? – tenpn Feb 17 '11 at 09:44
  • @tenp, yes, that's true, but as I explain in the top of the answer, there is no other way I can think of. You can surely create derivatives from this, but all of them will suffer from the same mantenance problem. – Baltasarq Feb 17 '11 at 10:04
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
Bojan Komazec
  • 9,216
  • 2
  • 41
  • 51
  • Cool, this will get the job done but if I understand correctly you will have to copy and paste all those lines in `Motorcar::Motorcar()` into the constructors of all further derived classes, right? This seems like a bit of pain. – elliot42 Feb 17 '11 at 21:04
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;
}
globalheap
  • 107
  • 4