Отражать дерево наследования классов в C ++? - PullRequest
1 голос
/ 17 февраля 2011

Скажем, у меня есть следующие классы в C ++, и я хочу проверить их наследование:

Vehicle

Motorcar является Vehicle
Aircraft является Vehicle

Biplane является Aircraft является Vehicle
Helicopter это Aircraft это Vehicle.

Я хочу написать метод 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().

Есть ли элегантный способ написать это?

(Спасибо за ваши мысли!)

Ответы [ 6 ]

10 голосов
/ 17 февраля 2011

Решение 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Вертолет - 8Aircraft - 7Техника

См. В идеоне: http://www.ideone.com/5PoJ0

Вы можете снять украшение, если хотите.Но это будет зависеть от компилятора! Здесь - это версия, которая использует функцию remove_decoration для снятия украшения, а затем выводится:

Вертолет - Самолет - Транспортное средство

Кстати, как я уже сказал, реализация функции remove_decoration зависит от компилятора;Кроме того, это может быть написано более корректно, поскольку я не знаю всех случаев, которые рассматривает GCC, в то время как искажает имена классов.Но я надеюсь, вы поняли основную идею.


Решение 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

4 голосов
/ 17 февраля 2011

Если вам нужен рекурсивный подход, вы можете сделать это с помощью виртуальных функций и явных вызовов функций с областью действия:

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" )
2 голосов
/ 17 февраля 2011

[...], но пытаюсь избежать копирования и вставки getClassLineage ().

Насколько я знаю, это невозможно.С ++ не имеет отражения сам по себе, поэтому программист должен сам выполнять работу.Следующая версия 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.

0 голосов
/ 17 февраля 2011
#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;
}
0 голосов
/ 17 февраля 2011

При использовании 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
0 голосов
/ 17 февраля 2011

Вам нужно статическое поле для хранения линии, и у каждого класса будет своя собственная линия, добавленная в его собственное статическое поле.

Если вы думаете об использовании typeid () или чего-то подобного, то естьболее сложный, но позволит избежать повторения метода getClassLineage () , помните, что атрибут поля имени раздражает (причина этого мне не известна) не истинное имя класса, а строка, которая можетбудь то имя или любое другое искаженное имя (т. е. неопределенное представление).

Вы можете легко применить рекурсивный подход как тот, который вы предлагаете, если мы использовали 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...