Как избежать dynamic_cast при реализации виртуальных функций в производном классе - PullRequest
3 голосов
/ 08 июля 2010

Вот пример кода, объясняющего, чего я пытаюсь достичь.

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

Однако различные производные объекты несовместимы друг с другом в том, что касается этих операций. Мой вопрос заключается в том, могу ли я избежать использования RTTI для того, чтобы, например, bool производный 2 :: идентичный (const base * other2), утверждал (или другой механизм выхода), где other2 не имеет типа производного 2.

Одной из альтернатив будет шаблон алгоритма функции для конкретного производного объекта, но это будет означать, что его реализация должна будет жить в заголовочном файле, чего я не хочу делать, так как 1) Изменение кода алгоритма для теста цели могут вызвать перекомпиляцию больших частей кода. 2) Реализация алгоритма будет отображаться в заголовке, а не жить в исходном файле, скрытом от конечного пользователя.

Заголовочный файл

#include <list>

class base
{
public:
 virtual float difference(const base*) const = 0;
 virtual bool identical(const base*) const = 0; 
};


class derived1 : public base
{
 public:
 float difference(const base* other1) const
 {
  // other1 has to be of type derived1
            if(typeid(other1) == typeid(this))
            {
                    // process ...
            }
            else
            {
                    assert(0);
            }
  return 1;
 }

 bool identical(const base* other1) const
 {
  // other1 has to be of type derived1
            if(typeid(other1) == typeid(this))
            {
                    // compare...
            }
            else
            {
                    assert(0);
            }
  return true;
 }
};


class derived2 : public base
{
 public:
        float difference(const base* other2) const
        { 
             // process ...
  // other2 has to be of type derived2
            return 2;
        }

 bool identical(const base* other2) const
        {
                // do comparison
  // derived1 and derived2 cannot be compared
                return true;
        }
};

// Declaration
int algorithm(std::list<base*>& members);

Реализация алгоритма Исходный файл

#include "header_file_containing_base"
int algorithm(std::list<base*>& members)
{
 // This function only relies on the interface defined in base
 // process members;

 return 1;
}

Основная программа

int main()
{
  // Create lists of derived1 and derived2
  // Run algorithm on these lists
}

Ответы [ 3 ]

2 голосов
/ 08 июля 2010

Вы можете использовать двойную отправку (http://en.wikipedia.org/wiki/Double_dispatch)

1 голос
/ 08 июля 2010

Ну, есть одна простая вещь: сохранить реальный тип как член.

  • An enum, группирующий все типы.Это станет громоздким, если у вас их много.
  • Фабрика для генерации идентификаторов (используя шаблоны для генерации только одного идентификатора на элемент)
  • ...

Я проиллюстрирую заводской идентификатор:

class IdFactory
{
public:
  template <class T>
  static size_t GetId(T const&) // argument deduction
  {
    static size_t const Id = GetIdImpl();
    return Id;
  }

private:
  static size_t GetIdImpl()
  {
    static size_t Id = 0;
    return ++Id;
  }
}; // class IdFactory

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

class Base
{
public:
  explicit Base(size_t id): mId(id) {}
  size_t const mId; // meaningless to change it afterward...

private:
};

class Derived: public Base
{
public:
  explicit Derived(): Base(IdFactory::GetId(*this)) {}
};

Затем вы можете использовать элемент mId для тестирования.Обратите внимание, что, поскольку он const, он может быть выставлен ... в противном случае вы можете создать встроенный const геттер ...

float Derived::difference(const Base& rhs)
{
  assert( IdFactory::GetId(*this) == rhs.mId );

  // ...
}

Стоимость здесь незначительна:

  • GetId является встроенным, поэтому вызов функции
  • GetId не инициализируется лениво, за исключением инициализации, это означает, что элемент static был инициализирован: обычно он реализован как ifутверждение, какое условие всегда оценивается как истинное (кроме первого раза).
  • == обычно быстрое;)

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

Существует также решение без сохранения, которое включает в себя вызов виртуальной функции:

class Other: public Base
{
public:
  virtual size_t id() const { return IdFactory::GetId(*this); }
};

Это легче реализовать на практике, так как не хранит constчлен означает, что вам не нужно писать задание самостоятельно.

0 голосов
/ 09 июля 2010

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

...