Сравнение производных классов в C ++ без динамического приведения или статического понижения - PullRequest
9 голосов
/ 26 января 2012

Я пытаюсь сравнить объекты общего базового класса вместе.Сравнение должно завершиться неудачно (например, вывести строку с ошибкой) в любом случае, когда два объекта различаются по классу или отличаются по значениям, характерным для данного объекта.В идеале сравнение должно быть каким-то образом принудительным, чтобы новый производный класс также должен был написать функцию сравнения для членов своего класса.Вот пример кода:

#include <iostream>
#include <string>
#include <vector>

class Vehicle 
{ 
public:
  virtual std::string compareTo(Vehicle* v) = 0;
};

class Bicycle : public Vehicle
{
public:
  Bicycle() { color_ = "red"; }
  std::string compareTo(Vehicle* v) { return "We're different vehicles."; }
  std::string compareTo(Bicycle* b) { return color_.compare(b->color_) ? "We're different bicycles." : "We're the same bicycle."; }

private:
  std::string color_;
};

class Car : public Vehicle
{
public:
  Car() { style_ = "sedan"; }
  std::string compareTo(Vehicle* v) { return "We're different vehicles."; }
  std::string compareTo(Car* c) { return style_.compare(c->style_) ? "We're different cars." : "We're the same car."; }

private:
  std::string style_;
};

int main()
{
  Vehicle* compareFrom = new Bicycle();

  std::vector<Vehicle*> compareTos;
  compareTos.push_back(new Bicycle());
  compareTos.push_back(new Car());

  std::vector<Vehicle*>::iterator it;
  for (it = compareTos.begin(); it != compareTos.end(); ++it)
    std::cout << compareFrom->compareTo(*it) << std::endl;

  return 0;
}

В настоящее время вывод (который вы можете увидеть здесь ) говорит: «Мы разные транспортные средства».Я знаю, что это происходит, потому что я использую указатель абстрактной базы.Проблема в том, как это исправить!

Результат, который я хотел бы получить , состоит в том, что велосипеды выдают, что они одинаковые, потому что они имеют одинаковый цвет.Велосипеды и машины должны выводить, что это разные транспортные средства.Велосипеды разных цветов и машины разных стилей также должны показывать, что они разные.Я чувствую, что должен быть отличный шаблон для решения этой проблемы, но я устаю от проблем с динамическим приведением или небезопасным понижением.Кроме того, я хотел бы, чтобы функция сравнения применялась среди членов одного класса (поэтому велосипеды должны иметь возможность сравнивать с другими велосипедами).

Ответы [ 4 ]

11 голосов
/ 26 января 2012

Требуется Множественная отправка (т.е. выберите, какую функцию вызывать динамически на основе более чем одной переменной, а не только «this»).Это потому, что вам нужно как-то проверить тип, иначе компилятор выполнит статический анализ типов и выберет, какую функцию вызывать (виртуальную в Vehicle).

Никакого обхода.dynamic_cast здесь ваш друг, но вы можете захотеть использовать свою собственную систему RTTI по ​​причинам производительности (или по другим причинам).(Статья в википедии показывает один способ ..)

 std::string Bicycle::compareTo(Vehicle* v) { 
    if (Bicycle* b = dynamic_cast<Bicycle*>(v)) {
       return compareTo(b);
    } else {
       return "We're different vehicles."; 
    }
 }

В библиотеке Loki C ++ есть реализация этого шаблона, которая может помочь, если у вас есть много типов, которые нужно сравнивать.

Многократная диспетчеризация не поддерживается ни языком C ++, ни большинством основных языков.Было предложение добавить его в C ++ 11, хотя, см. Этот вопрос и статью Бьярне .Я думаю, что он был отклонен из-за (известных и неизвестных) проблем с динамическим связыванием, о которых, к сожалению, стандарт C ++ ничего не знает.

6 голосов
/ 26 января 2012

У вашего кода большая проблема в том, что его нелегко расширить (нарушает принцип открытия / закрытия ).Однако вы можете делегировать сравнение методу базового класса.

Кроме того, если вы захотите применить семантику (это хорошо), вы не сможете обойти даункинг, извините.

Чтобы сделать его надежным и расширяемым,

  1. Сделать базовый метод чисто виртуальным
  2. Обеспечить реализацию базового метода (да, это работает! Даже если он чисто виртуальный), который сравниваеттипы объектов
  3. В производных классах используйте реализацию базового класса для проверки на равенство типов, а затем выполните фактическую проверку логики.
#include <iostream>
#include <iomanip>
#include <string>
#include <typeinfo>

struct vehicle {
    virtual bool compare_to(vehicle const& other) const = 0;
};

bool vehicle::compare_to(vehicle const& other) const {
    return typeid(*this) == typeid(other);
}

struct car : vehicle {
    std::string color;

    car(std::string const& color) : color(color) { }

    bool compare_to(vehicle const& other) const {
        bool result = vehicle::compare_to(other);
        return result and (color == static_cast<car const&>(other).color);
    }
};

struct bike : vehicle {
    int spokes;

    bike(int spokes) : spokes(spokes) { }

    bool compare_to(vehicle const& other) const {
        bool result = vehicle::compare_to(other);
        return result and (spokes == static_cast<bike const&>(other).spokes);
    }
};

int main() {
    car c1("blue");
    car c2("red");
    bike b1(42);

    std::cout << std::boolalpha;
    std::cout << c1.compare_to(c2) << "\n"
              << c1.compare_to(b1) << "\n"
              << c1.compare_to(c1) << "\n";
}

Приведенный выше кодstatic_cast безопасен, так как мы заранее убедились, что тип один и тот же, поэтому приведение никогда не завершится неудачей.

Обратите внимание, что использование typeid здесь совершенно законно.Это даже не должно быть очень неэффективно, так как нет никакой глубокой иерархии типов, чтобы идти.Но если вы хотите сделать это более эффективным, вы можете реализовать простой собственный механизм, который использует статическую таблицу в базовом классе для сопоставления каждого созданного экземпляра с уникальным идентификатором типа (например, std::map<vehicle*, type_id>, где type_id - это простой старыйenum) и выполните простой поиск.

… Или используйте dynamic_cast, на самом деле.

4 голосов
/ 26 января 2012

Обычно я реализую это, используя «добрый» член в базовом классе. Я считаю, что это имеет несколько преимуществ:

  • Производительность - не требуется виртуальный вызов функции и динамическое приведение
  • Используя разные «биты» для каждого типа класса, можно проводить сравнения на более высоком уровне. Например, «одноколесный велосипед» и «велосипед» могут работать от человека, поэтому вы можете легко проверить это отдельно от их основного вида.

Тип вида будет выглядеть следующим образом:

    enum Kind {
      HUMAN_POWERED  = (0x1 << 0),
      MOTOR_POWERED  = (0x1 << 1),
      BICYCLE        = (0x1 << 2) | HUMAN_POWERED,
      UNICYCLE       = (0x1 << 3) | HUMAN_POWERED,
      CAR            = (0x1 << 4) | MOTOR_POWERED
    };

Теперь можно проверить, что АВТОМОБИЛЬ не является ВЕЛОСИПЕДОМ, но также если MOTOR_POWERED двух типов или нет!

bool areSameClass (Vehicle const & lhs, Vehicle const & rhs)
{
  return (lhs->getKind () & rhs->getKind ()) & (HUMAN_POWERED | MOTOR_POWERED);
}
0 голосов
/ 26 января 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...