2015-03-07 10 views
1

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

#include <iostream> 

struct Base { 

    virtual bool operator==(const Base& rhs) const 
     {return rhs.equalityBounce(this);} 

    virtual bool equalityBounce(const Base* lhs) const = 0; 
    virtual bool equalityCheck(const Base* lhs) const = 0; 
}; 

struct A : public Base { 

    A(int eh) : a(eh) {} 
    int a; 

    virtual bool equalityBounce(const Base* lhs) const{ 
     return lhs->equalityCheck(this); 
    } 

    virtual bool equalityCheck(const Base* rhs) const {return false;} 
    virtual bool equalityCheck(const A* rhs) const {return a == rhs->a;} 
}; 


int main() { 

    Base *w = new A(1), *x = new A(2); 

    std::cout << (*w == *w) << "\n"; 
    std::cout << (*w == *x) << "\n"; 
} 

Я понимаю, что код, как записываемые неудачу, потому что LHS в equalityBounce() является базовой *, так что даже не знает о версии equalityCheck(), которая принимает A *. Но я не знаю, что с этим делать.

+0

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

+0

Возможный дубликат [C++ Double Dispatch for Equals()] (http://stackoverflow.com/questions/7393458/c-double-dispatch-for-equals) – Pradhan

+0

@Pradhan ВНИМАНИЕ: Это не полный дубликат другого SO вопрос. Оба относятся к равным с двойной отправкой. Это вводит ограничение расширяемости, которое не упоминается и не рассматривается в других ответах. – Christophe

ответ

1

Почему он не работает

Проблема с двойной реализации доставки является то, что вы ожидаете, что наиболее специфичный equalityCheck() называется.

Но ваша реализация суммарно на основе полиморфного базового класса, и equalityCheck(const A*) перегрузок, но не отменяетequalityCheck(const Base*)!

Otherwhise сказал во время компиляции компилятор знает, что A::equalityBounce() можно назвать equalityCheck(A*) (потому что this является A*), но, к сожалению, она называет Base::equalityCheck(), которая не имеет специализированную версию для A* с параметром.

Как его реализовать?

Для двойной отправки на работу вам необходимо иметь конкретные реализации двойного отправленного равенстваCheck() в базовом классе.

Для этого на работу, база должна быть в курсе своих потомков:

struct A; 

struct Base { 

    virtual bool operator==(const Base& rhs) const 
    { 
     return rhs.equalityBounce(this); 
    } 

    virtual bool equalityBounce(const Base* lhs) const = 0; 
    virtual bool equalityCheck(const Base* lhs) const = 0; 
    virtual bool equalityCheck(const A* lhs) const = 0; 
}; 

struct A : public Base { 
    ... 
    bool equalityBounce(const Base* lhs) const override{ 
     return lhs->equalityCheck(this); 
    } 
    bool equalityCheck(const Base* rhs) const override { 
     return false; 
    } 
    bool equalityCheck(const A* rhs) const override{ 
     return a == rhs->a; 
    } 
}; 

Обратите внимание на использование override, чтобы убедиться, что функция действительно перекрывает виртуальную функцию базы.

С этой реализации он будет работать, потому что:

  • A::equalityBounce() позвонит Base::equalityCheck()
  • среди всех перегруженных версий этой функции, она будет выбирать Base::equalityCheck(A*), потому что this является A*
  • вызываемый объект Base *lhs будет называть его equalityCheck(A*). Если lhs - A*, то он будет идти за A::equalityCheck(A*), что даст ожидаемый (правильный) результат. Поздравляем!
  • предположим, что lhs будет указателем на другой класс X, также полученный от Base.В этом случае lhs->equalityCheck(A*) будет звонить X::equalityCheck(A*) и может также вернуть правильный ответ, учитывая, что вы сравнили бы X с A.

Как сделать его расширяемым? Карта двойной отправки!

Проблема с двойной отправкой с использованием строго типизированного языка заключается в том, что объект «отскок» должен знать, как сравнивать с конкретными (известными заранее) классами. Поскольку ваш исходный объект и ваш возвращенный объект имеют один и тот же полиморфный базовый тип, база должна, следовательно, знать все задействованные типы. Этот дизайн серьезно ограничивает экстенсивность.

Если вы хотите, чтобы иметь возможность добавить любой производный тип, не зная об этом заранее в базовом классе, то вы должны пройти через динамических типов (будь то dynamic_cast или TypeId):

Я предлагаю вам здесь предложение по динамической БЕЗОПАСНОСТИ. Он использует единую депешу для сравнения двух объектов одного и того же типа, и двойных диспетчерской карты для сравнения различных типов между ними (возвращением по умолчанию ложного, если ничего не было объявлено):

struct Base { 
    typedef bool(*fcmp)(const Base*, const Base*); // comparison function 
    static unordered_map < type_index, unordered_map < type_index, fcmp>> tcmp; // double dispatch map 

    virtual bool operator==(const Base& rhs) const 
    { 
     if (typeid(*this) == typeid(rhs)) { // if same type, 
      return equalityStrict(&rhs);  // use a signle dispatch 
     } 
     else {        // else use dispatch map. 
      auto i = tcmp.find(typeid(*this)); 
      if (i == tcmp.end()) 
       return false;    // if nothing specific was foreseen... 
      else { 
       auto j = i->second.find(typeid(rhs)); 
       return j == i->second.end() ? false : (j->second)(this, &rhs); 
      } 
     } 
    } 
    virtual bool equalityStrict(const Base* rhs) const = 0; // for comparing two objects of the same type 
}; 

Аналого класс будет тогда быть rewrtitten как:

struct A : public Base { 
    A(int eh) : a(eh) {} 
    int a; 
    bool equalityStrict(const Base* rhs) const override { // how to compare for the same type 
     return (a == dynamic_cast<const A*>(rhs)->a); 
     } 
}; 

с помощью этого кода вы можете сравнить любые объекты с объектом одного и того же типа. Теперь, чтобы показать расширяемость, я создал struct X с теми же членами, что и A. Если я хочу, чтобы позволить copare А с X, я просто определить функцию сравнения:

bool iseq_X_A(const Base*x, const Base*a) { 
    return (dynamic_cast<const X*>(x)->a == dynamic_cast<const A*>(a)->a); 
} // not a member function, but a friend. 

Затем сделать динамическую двойную работу dipatch, я должен добавить эту функцию к карте двойной доставки:

Base::tcmp[typeid(X)][typeid(A)] = iseq_X_A; 

Тогда resutls легко проверить:

Base *w = new A(1), *x = new A(2), *y = new X(2); 
std::cout << (*w == *w) << "\n"; // true returned by A::equalityStrict 
std::cout << (*w == *x) << "\n"; // false returned by A::equalityStrict 
std::cout << (*y == *x) << "\n"; // true returned by isseq_X_A 
1

Если вам нужен виртуальный оператор равенства, и вы хотите избежать dynamic_cast, вы можете добавить тип перечисления в ваш базовый класс, который указывает тип и создать виртуальный приемник, а затем сделать статическую трансляцию, если она соответствует.

enum ClassType { AType, BType }; 
Class A 
{ 
public: 

virtual ClassType getType(); 
virtual bool operator ==(const A& a) const; 
}; 

// in B implementation 

virtual bool B::operator ==(const A& a) 
{ 
if (!A::operator==(a)) return false; 
if(a.getType() != ClassType::BType) return false; 
const B& b = static_cast <const& B>(a); 
// do B == checks 
return true; 
} 
+0

Вы правы: это альтернатива. Однако с вашим перечислением и 'getType()' у вас будет решение, очень близкое к альтернативе 'dynamic_cast'. Это, безусловно, допустимое решение, когда нужно сравнивать только объекты одного и того же производного класса. Но если бы у вас было бы много разных правил сравнения/преобразования между «сестринскими классами», у вас было бы очень много сложного 'operator ==', чтобы обеспечить все возможные комбинации подтипов. – Christophe

+0

Правда, идея состоит в том, чтобы избежать накладных расходов на производительность серии динамических бросков. Ответчик специально искал решение для иерархии типов. Если другие типы сравнений действительны, то вы правы, что было бы лучше предоставить дополнительные бесплатные функции, которые реализуют это поведение. – GameSalutes

0

Altough медленно из-за dynamic_cast, я думаю, что наиболее удобным решением является следующая:

#include <iostream> 

struct Base { 
    virtual bool operator==(const Base& rhs) const 
    { return equalityBounce(&rhs); } 

    virtual bool equalityBounce(const Base* lhs) const = 0; 
}; 

template<typename Derived> 
struct BaseHelper : public Base 
{ 
    bool equalityBounce(const Base* rhs) const 
    { 
     const Derived* p_rhs = dynamic_cast<const Derived*>(rhs); 

     if (p_rhs == nullptr) 
      return false; 
     else 
      return p_rhs->equalityCheck 
      (reinterpeter_cast<const Derived*>(this)); 
    } 

    virtual bool equalityCheck(const Derived*) const = 0; 
}; 

struct A : public BaseHelper<A> { 

    A(int eh) : a(eh) {} 
    int a; 

    virtual bool equalityCheck(const A* rhs) const 
    { return a == rhs->a; } 
}; 


int main() { 

    Base *w = new A(1), *x = new A(2); 

    std::cout << (*w == *w) << "\n"; // Prints 1. 
    std::cout << (*w == *x) << "\n"; // Prints 0. 
} 

Таким образом, вам нужно только заботиться о сравнении объектов одного и того же производного типа «я». Вам не нужно писать больше перегрузок, потому что BaseHelper выполняет всю вспомогательную работу для вас.

 Смежные вопросы

  • Нет связанных вопросов^_^