Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the wrong function being executed?

I ran into a confusing situation today that I'm hoping someone can explain to me.

I have a C++ program with 4 classes:

  • A Base class that just acts as a common interface,
  • An Enroll class, which subclasses Base and has a pure virtual enroll() method,
  • A Verify class, which also subclasses Base and has a pure virtual verify() method,
  • A Both class that subclasses both Enroll and Verify and provides implementations for enroll() and verify()

Like so:

 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"); }
 };

Instances of Both are instantiated in a non-member function, which just creates a new Both and returns the pointer:

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

Finally, there's a Registry class that basically just acts as an Enroll/Verify factory. It uses a pair of function pointers to the createInstanceOfBoth() function to provide an instance of Enroll or 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;
};

And here's the problem. When I call getEnroll() on my Registry object and invoke enroll() on the object that's returned, I see the proper, expected output: Enrolling.. But when I call getVerify() and invoke verify() on the object that's returned, the enroll() method is executed again!

Code:

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

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

Output:

Enrolling.
Enrolling.

I've noticed that if I change the order of Enroll and Verify in the declaration of the Both class (class Both : public Verify, public Enroll {...}), the opposite effect will happen:

Verifying.
Verifying.

I've identified a workaround in which, instead of using a single createInstanceOfBoth() function to create my Enroll and Verify objects, I use two differently named functions with the same body but different return types:

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

Then, in the Registry class, I create my function pointers to these functions instead:

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

When I run the program now, I get the expected output:

Enrolling.
Verifying.

My question is this: why doesn't the first way of doing it work? I suspect that it has something to do with casting createInstanceOfBoth() to a function pointer with a different return type, but I don't fully understand exactly what's happening. I'm fine with using my workaround at the moment, but I'm curious: is there a way of doing this with just the single createInstanceOfBoth() function instead of having to use two identical functions but with different return types?

To save the time and hassle of putting this together into a compilable example, I've posted this code to https://gist.github.com/833304 for download.

like image 266
RTBarnard Avatar asked Feb 18 '11 05:02

RTBarnard


1 Answers

That function pointer cast (treating a function returning Both* as returning Verify* or Enroll*) is incorrect; with multiple inheritance, the different base classes within an object do not necessarily start at the beginning of the object. The function pointer cast is not doing the offsetting operation that is required for the derived-to-base pointer casts from Both* to the base pointer types.

You can have a single createInstanceOfBoth function, but it would need to return Both*; the code that calls it would then do the derived-to-base cast with that result.

like image 199
Jeremiah Willcock Avatar answered Oct 20 '22 04:10

Jeremiah Willcock