Как лучше всего вызвать функцию в дочернем классе неправильным образом?

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

Допустим, у нас есть упрощенный класс:

class Foo
{
  void a();
  virtual void b();
}

а потом еще класс

class Bar : public Foo
{
  void a();
}

Если общая часть программы обрабатывает все эти классы как базовый класс типа Foo, как мне наилучшим образом вызвать «правильную» версию функции a внутри b? Так как есть вероятность, что объект имеет тип Bar. И скажем, вы, по устаревшим причинам, не можете изменить существующий код и сделать базовый класс функцией virtual и т. д.

Что я сделал, так это сделал a виртуальным в базовом классе и реализовал b в Bar, поскольку у меня была такая возможность. Но скажем ради аргумента, что это было невозможно или недопустимо. Насколько «неправильным» было бы реализовать обходной путь, используя что-то вроде этого.

void b() {
  ...

  Bar* dabar;
  if((dabar = dynamic_cast<Bar*>(this)) != NULL) {
    dabar->a();
  }
  else {
    a();
  }
}

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


person inquam    schedule 03.07.2012    source источник
comment
Вы выводите из метода a?   -  person Mare Infinitus    schedule 03.07.2012
comment
Только что отредактировал это :)... Моя ошибка;)   -  person inquam    schedule 03.07.2012
comment
Это сработает. О чем ты спрашиваешь?   -  person Kos    schedule 03.07.2012
comment
Почему бы тебе просто не сделать виртуалку?   -  person Mare Infinitus    schedule 03.07.2012
comment
Mare Infinitus: Да, но я сказал просто ради аргумента ;)... Люди, которые когда-то делали что-то подобное, возможно, не знали о виртуальных функциях. Я понятия не имею :)... Моя первая реакция была, какого черта они сделали это так :P   -  person inquam    schedule 03.07.2012
comment
Кос: Мой вопрос, вероятно, в том, есть ли какие-либо проблемы с этим и т. Д. Я могу думать, что поиск в таблице виртуальных функций быстрее, чем попытка преобразования.   -  person inquam    schedule 03.07.2012


Ответы (3)


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

Если у вас нет такой возможности, то по какой бы то ни было причине ваш код будет работать, за исключением случаев, когда вы хотите получить от Bar. Как вы думаете, что произойдет, когда у вас будет бар*, и вы сделаете колл "а" на нем.

Нравиться:

Chair : public Bar {...}

Bar *bar = new Chair;
bar->a();
// what the heck?

И, конечно же, dynamic_cast будет означать серьезный проигрыш в производительности.

Пишите чистый код, пишите то, что хотите выразить, не используйте уловки!

person Mare Infinitus    schedule 03.07.2012

Насколько неправильно было бы реализовать обходной путь, используя что-то вроде этого.

void b() {
  ...

  Bar* dabar;
  if((dabar = dynamic_cast<Bar*>(this)) != NULL) {
    dabar->a();
  }
  else {
    a();
  }
}

Это было бы перебором. Простой вызов a() в виртуальной функции делает свое дело.

#include <iostream>

class Base
{
public:
    virtual int tryit()
    {
        return foo();
    }
private:
    int foo(){ return 1; }
};

class Derived: public Base
{
public:
    virtual int tryit()
    {
        return foo();
    }

private:
    int foo(){ return 2; }
};

int main()
{
    Base *A =  new Derived();
    std::cout << std::endl << A->tryit();
}

выход:

 2
person SingerOfTheFall    schedule 03.07.2012
comment
Функция foo() — это не виртуальная функция a() из примера вопроса. tryit() — это виртуальный b() из примера вопроса. - person SingerOfTheFall; 03.07.2012
comment
Почти... Но, как уже упоминалось, все они обрабатываются как базовый класс. Поэтому выполните приведение B к его базовому классу Base перед запуском функции. Затем вы обнаружите, что tryit() больше не возвращает 1 и 2. - person inquam; 03.07.2012
comment
Нет, он до сих пор так работает. Я отредактировал основную функцию, теперь она работает с указателем базового класса, проверьте. - person SingerOfTheFall; 03.07.2012
comment
Ах, пропустил, что вы добавили вторичную виртуальную реализацию tryit() в Derived. - person inquam; 12.07.2012

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

Теоретически вы не делаете ничего плохого. Это четко определенное поведение.

Просто это не рекомендуется следовать. Это делает код беспорядочным и запутанным.
Более того, будет неуправляемо, если bar будет производным от какого-либо другого дочернего класса в будущем.

person iammilind    schedule 03.07.2012
comment
Я бы сказал, что если функция a() (в данном случае) не является общедоступной, это не так уж и плохо. Однако я согласен, что было бы лучше, если бы он был виртуальным. - person SingerOfTheFall; 03.07.2012
comment
@SingerOfTheFall, OP уже упоминает, что Foo::a() нельзя сделать virtual по некоторым причинам. - person iammilind; 03.07.2012
comment
Вот почему я говорю, что было бы лучше, если бы это было... вместо того, чтобы ради всех милых котят, вы должны изменить свой код и сделать его.... - person SingerOfTheFall; 03.07.2012