I ran into a confusing situation today that I'm hoping someone can explain to me.
I have a C++ program with 4 classes:
Base
class that just acts as a common interface,Enroll
class, which subclasses Base
and has a pure virtual enroll()
method,Verify
class, which also subclasses Base
and has a pure virtual verify()
method,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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With