Почему выполняется неправильная функция? - PullRequest
3 голосов
/ 18 февраля 2011

Сегодня я столкнулся с запутанной ситуацией, которая, я надеюсь, кто-нибудь сможет мне объяснить.

У меня есть программа на C ++ с 4 классами:

  • A Base класс, который действует как общий интерфейс,
  • Класс Enroll, который подклассов Base и имеет чисто виртуальный метод enroll(),
  • A Verify класс, который также является подклассом Base и имеет чисто виртуальный метод verify(),
  • A Both класс, который подклассов и Enroll и Verify и обеспечивает реализации для enroll() и verify()

Вот так:

 class Base {
    public:
       Base () { }
       virtual ~Base () { }
 };

 class Enroll : public virtual Base {
    public:
       virtual ~Enroll () { }
       virtual void enroll () = 0;
 };

 class Verify : public virtual Base {
    public:
       virtual ~Verify () { }
       virtual void verify () = 0;
 };

 class Both : public Enroll, public Verify {
    public:
       virtual ~Both () { }

       virtual void enroll () { printf ("Enrolling.\n"); }
       virtual void verify () { printf ("Verifying.\n"); }
 };

Экземпляры Both создаются в функции, не являющейся членом, которая просто создает новый Both и возвращает указатель:

Both* createInstanceOfBoth () {
   return new Both();
}

Наконец, есть класс Registry, который в основном действует как фабрика Enroll / Verify. Он использует пару указателей на функцию createInstanceOfBoth() для предоставления экземпляра Enroll или Verify:

typedef Enroll* (*EnrollGenerator) ();
typedef Verify* (*VerifyGenerator) ();

class Registry {
   public:
      Registry () {
          enrollGenerator = (EnrollGenerator)&createInstanceOfBoth;
          verifyGenerator = (VerifyGenerator)&createInstanceOfBoth;              
      }

      Enroll* getEnroll () { return enrollGenerator (); }
      Verify* getVerify () { return verifyGenerator (); }

      EnrollGenerator enrollGenerator;
      VerifyGenerator verifyGenerator;
};

А вот и проблема. Когда я вызываю getEnroll() для моего Registry объекта и вызываю enroll() для возвращаемого объекта, я вижу правильный, ожидаемый вывод: Enrolling.. Но когда я вызываю getVerify() и вызываю verify() для возвращаемого объекта, метод enroll() выполняется снова !

Код:

int main () {
   Registry registry;
   Enroll *enroller;
   Verify *verifier;

   enroller = registry.getEnroll ();
   verifier = registry.getVerify ();
   enroller->enroll ();
   verifier->verify ();
   return 0;
}

Выход:

Enrolling.
Enrolling.

Я заметил, что если я изменю порядок Enroll и Verify в объявлении класса Both (class Both : public Verify, public Enroll {...}), произойдет противоположный эффект:

Verifying.
Verifying.

Я нашел обходной путь, в котором вместо использования одной функции createInstanceOfBoth() для создания моих объектов Enroll и Verify я использую две функции с разными именами с одинаковым телом, но разными типами возвращаемых данных:

Enroll* createInstanceOfEnroll () {
   return new Both();
}
Verify* createInstanceOfVerify () {
   return new Both();
}

Затем в классе Registry вместо этого я создаю указатели на эти функции:

Registry () {
    enrollGenerator = &createInstanceOfEnroll;
    verifyGenerator = &createInstanceOfVerify;        
}

Когда я сейчас запускаю программу, я получаю ожидаемый результат:

Enrolling.
Verifying.

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

Чтобы сэкономить время и хлопоты, собрав их в скомпилированный пример, я разместил этот код на https://gist.github.com/833304 для загрузки.

1 Ответ

6 голосов
/ 18 февраля 2011

Это приведение указателя функции (обработка функции, возвращающей Both* как возвращение Verify* или Enroll*), неверна; при множественном наследовании разные базовые классы в объекте не обязательно начинаются с начала объекта. Приведение указателя функции не выполняет операцию смещения, которая требуется для приведения производного к основному указателю от Both* к базовым типам указателя.

Вы можете иметь одну createInstanceOfBoth функцию, но для этого потребуется вернуть Both*; код, который вызывает его, будет затем выполнять приведение от производной к базе с таким результатом.

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