Как обеспечить более одного переопределения для одной виртуальной функции - PullRequest
1 голос
/ 16 декабря 2010

У меня есть следующие классы:

class A {
};

class B : public A {
};

class P     {
private:
 std::list<A*> l
protected:
 virtual void DoIt(A* a) = 0;
public:
 void WorkerThread() { for (it=l.begin(); it!=l.end(); it++) DoIt(*it); }
};

class Q : public P
{
protected:
 void DoIt(A* a) { print("false"); }
 void DoIt(B* b) { print("true"); }
};

К сожалению, DoIt (B * b) никогда не будет вызван.DoIt (A * a) всегда будет вызываться, даже если я добавлю объекты B в список.

Что я могу сделать, чтобы вызвать DoIt (B * b)?Возможно ли достичь этого, если B не знает Q?Можно ли этого добиться, если без динамического приведения?

Спасибо

Ответы [ 6 ]

4 голосов
/ 16 декабря 2010

Ну, никто на самом деле прямо не ответил на ваш вопрос (ну, тяжело, постарался), так что я буду.Некоторые другие «ответы» здесь на самом деле более полезны для решения вашей проблемы.

Проблема в том, что void DoIt (B *) НЕ является переопределением виртуальной функции DoIt (A *).Это перегрузка .Существует огромная разница.

Когда вы говорите, что DoIt (B *) не вызывается, когда вы передаете B *, я должен предположить, что вы держите ссылки или указатели на вас Q через указатель на что-то более высокоевверх по иерархии.В этих случаях статическое разрешение имен находит только DoIt (A *), и, поскольку B * is-a A *, оно выгружается, и именно эта версия вызывается.Поскольку он является виртуальным, вызывается переопределение в Q.

Если у вас есть указатель на Q в качестве указателя на Q, и вы вызываете DoIt с помощью B *, должна вызываться функция DoIt (B *).,На этом этапе двойная диспетчеризация не требуется и не используется.

Двойная диспетчеризация требуется, когда у вас есть два абстрактных типа и функция, которая должна вести себя по-разному в зависимости от конкретных типов обеих абстракций.Это то, что вы пытаетесь сделать, когда вы вызываете DoIt с B на Q на более высоком уровне, чем обеспечивает статическое именование.Существует слишком много методов, которые отвечают различным потребностям, чтобы иметь возможность предложить одно решение вместо другого в вашем случае, не знаю точно, что вы пытаетесь решить.На самом деле, вам это может даже не понадобиться!Лучшим подходом для вас может быть реализация DoIt (B *) как виртуальной функции в верхней части вашей иерархии.

Я бы посоветовал вам взять книгу Андре Александреску «Современный дизайн C ++» и просмотреть ее.Он объясняет, чертовски крутая реализация посетителя, а также механизм множественной отправки, который масштабируется.Но не останавливайтесь на этом, есть и другие замечательные реализации, которые могут ответить на вопрос по-другому.

Удачи.

2 голосов
/ 16 декабря 2010

Вы ищете механизм двойной отправки, который не встроен в язык.Существуют разные подходы к тому, как это можно реализовать на основе шаблона посетителя.Google для двойной отправки в C ++.Обратите внимание, что это патч, и его нелегко распространить на большие иерархии:

struct visitor;
struct A {
   virtual void accept( visitor& v ) { v(*this); }
};
struct B {
   virtual void accept( visitor& v ) { v(*this); }
}; 
struct visitor {
   virtual void operator()( A& ) = 0;
   virtual void operator()( B& ) = 0; 
};
struct myvisitor : visitor {
   void operator( A& ) { std::cout << "A" << std::endl; }
   void operator( B& ) { std::cout << "B" << std::endl; }
};
int main() {
   std::vector<A*> data = ...
   myvisitor v;
   for ( std::vector<A*>::iterator it = data.begin(), end = data.end(); it != end; ++it )
   {
      (*it)->accept( v );
   }
}

Будет использован обычный механизм, а accept будет отправлен в окончательный переопределитель метода, который в свою очередь вызоветметод посетителя.Теперь в этот момент статический тип аргумента для посетителя operator() равен на самом деле фактический тип, с которым вы хотите вызвать функцию.

1 голос
/ 16 декабря 2010

DoIt(B* b) никогда не будет вызван, потому что вы никогда не передаете объекты типа B *, каждый раз, когда вы вызываете DoIt, по крайней мере в данном коде вы передаете объекты типа A*.

Рассмотрим ситуацию, когда переопределение Doit(A* a) не существовало.Ваш текущий код не будет компилироваться, поскольку компилятор не может неявно привести объект типа A* к B*.

0 голосов
/ 16 декабря 2010

Вы ищете несколько отправок или мультиметоды. В Википедии есть хороший пример для c ++; ссылка здесь .

0 голосов
/ 16 декабря 2010

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

Например, если вы не хотите, чтобы логика функциональности DoIt в самих классах A и B была виртуальнойфункция, то вы можете использовать метод dynamic_cast:

class A {
};

class B : public A {
};

class P : protected std::list<A*>
{
protected:
 virtual void DoIt(A* a) = 0;
public:
 void WorkerThread() { for (it=begin(); it!=end(); it++) DoIt(*it); }
};

class Q : public P
{
protected:
 void DoIt(A* a) {
  if(B *b = dynamic_cast<B*>(a)) {
   // It's a B*, you can "use" b here
   print("true");
  } else {
   // It's an A*
   print("false");
  }
 }
};
0 голосов
/ 16 декабря 2010

Каким вы ожидаете поведение, если кто-то передает A *, но базовый тип действительно B?

Возможно, вы ищете что-то вроде этого:

class A
{
public:
  virtual ~A() {}
  virtual bool isB() const { return false; }
};

class B : public A
{
public:
  bool isB() const { return true; }
};

void Q::DoIt( A* a )
{
   print( a->isB() ? "true" : "false" );
}
...