Virtual Method Behavior That I Don't Understand
I have the following code:
////////////////////////////////////////////////
// Content of file "classes.h"
#ifndef CLASSES_H
#include <iostream>
#include <string>
#define OVERRIDE 0
class BaseClass
{
public:
BaseClass (void) : m_override (OVERRIDE) {};
virtual ~BaseClass (void) {};
virtual void setProvisional (const char *pValueAsString) {
std::cout << "BaseClass const char *\n";
};
virtual void setProvisional (const std::string & value) {
std::cout << "BaseClass const std::string &\n";
};
int m_override;
};
class DerivedClass : public BaseClass
{
public:
DerivedClass (void) {};
virtual ~DerivedClass (void) {};
#if ((OVERRIDE == 1) || (OVERRIDE == 3))
virtual void setProvisional (const char *pValueAsString) {
std::cout << "DerivedClass const char *\n";
};
#endif // OVERRIDE
#if ((OVERRIDE == 2) || (OVERRIDE == 3))
virtual void setProvisional (const std::string & value) {
std::cout << "DerivedClass const std::string &\n";
};
#endif // OVERRIDE
};
#endif //CLASSES_H
////////////////////////////////////////////////
// Content of file "main.cpp"
#include <iostream>
#include <string>
#include "classes.h"
int main (int argc, char * const argv[]) {
DerivedClass testClass;
DerivedClass *pDerived = &testClass;
BaseClass *pBase = &testClass;
const char *pStr = "Should call char * version";
const std::string str ("Should call std::string & version");
std::cout << "//////////////////////////////////////////////\n";
std::cout << "Starting new test run\n";
std::cout << "\n OVERRIDE value is: " << testClass.m_override << "\n";
std::cout << "\nCalling as object.method:\n";
testClass.setProvisional (pStr);
testClass.setProvisional (str);
std::cout << "\nCalling as pObject->method:\n";
pDerived->setProvisional (pStr);
pDerived->setProvisional (str);
std::cout << "\nCalling as pBaseClass->method:\n";
pBase->setProvisional (pStr);
pBase->setProvisional (str);
return 0;
}
Depending on how I define OVERRIDE in classes.h, I can override none,
either, or both of the base class virtual methods.
I am seeing results that I do not understand:
//////////////////////////////////////////////
Starting new test run
OVERRIDE value is: 0
Calling as object.method:
BaseClass const char *
BaseClass const std::string &
Calling as pObject->method:
BaseClass const char *
BaseClass const std::string &
Calling as pBaseClass->method:
BaseClass const char *
BaseClass const std::string &
setProvisionalInheritanceTest has exited with status 0.
//////////////////////////////////////////////
Starting new test run
OVERRIDE value is: 2
Calling as object.method:
DerivedClass const std::string &
DerivedClass const std::string &
Calling as pObject->method:
DerivedClass const std::string &
DerivedClass const std::string &
Calling as pBaseClass->method:
BaseClass const char *
DerivedClass const std::string &
setProvisionalInheritanceTest has exited with status 0.
//////////////////////////////////////////////
Starting new test run
OVERRIDE value is: 3
Calling as object.method:
DerivedClass const char *
DerivedClass const std::string &
Calling as pObject->method:
DerivedClass const char *
DerivedClass const std::string &
Calling as pBaseClass->method:
DerivedClass const char *
DerivedClass const std::string &
setProvisionalInheritanceTest has exited with status 0.
I don't have a run result for the case of (OVERRIDE == 1), because it
doesn't compile. I get the following errors (This is on a Mac, using
the latest/greatest Xcode):
main.cpp: In function 'int main(int, char* const*)':
main.cpp:19: error: no matching function for call to
'DerivedClass::setProvisional(const std::string&)'
classes.h:38: note: candidates are: virtual void
DerivedClass::setProvisional(const char*)
main.cpp:23: error: no matching function for call to
'DerivedClass::setProvisional(const std::string&)'
classes.h:38: note: candidates are: virtual void
DerivedClass::setProvisional(const char*)
These correspond to the lines:
testClass.setProvisional (str);
and
pDerived->setProvisional (str);
If I move the code to Windows and do the same test using VS2005, I get
the same results. (Different compiler errors, but they hint at the same
problem.)
The (OVERRIDE == 0) and (OVERRIDE == 3) cases behave as I expected. I
can almost understand the (OVERRIDE == 2) case. It seems that the calls
using either a derived class instance or a pointer to a derived class
cause the compiler to first examine the derived class methods. It
determines that it can promote (const char *) to (const std::string),
so does that and calls the derived class method that takes (const
std::string &). I can almost understand that, but why does it do this
preferentially when it has inherited a method with the exact required
signature from the base class?
The (OVERRIDE == 1) case seems to be a variation on the same behavior,
but again the derived class inherited void setProvisional(const
std::string &) from the base class. Why is that masked?
I got out my Stroustrup and Scott Meyers books and didn't find a clear
answer. If I separate the (non-virtual) interface and (virtual)
implementation, then I get the expected behavior.
If I change classes.h to look like this:
#ifndef CLASSES_H
#include <iostream>
#include <string>
#define OVERRIDE 3
class BaseClass
{
public:
BaseClass (void) : m_override (OVERRIDE) {};
virtual ~BaseClass (void) {};
void setProvisional (const char *pValueAsString) {
setProvisionalImpl (pValueAsString);
};
void setProvisional (const std::string & value) {
setProvisionalImpl (value);
};
protected:
virtual void setProvisionalImpl (const char *pValueAsString) {
std::cout << "BaseClass const char *\n";
};
virtual void setProvisionalImpl (const std::string & value) {
std::cout << "BaseClass const std::string &\n";
};
public:
int m_override;
};
class DerivedClass : public BaseClass
{
public:
DerivedClass (void) {};
virtual ~DerivedClass (void) {};
protected:
#if ((OVERRIDE == 1) || (OVERRIDE == 3))
virtual void setProvisionalImpl (const char *pValueAsString) {
std::cout << "DerivedClass const char *\n";
};
#endif // OVERRIDE
#if ((OVERRIDE == 2) || (OVERRIDE == 3))
virtual void setProvisionalImpl (const std::string & value) {
std::cout << "DerivedClass const std::string &\n";
};
#endif // OVERRIDE
};
#endif //CLASSES_H
Then all test cases compile and they run like this:
//////////////////////////////////////////////
Starting new test run
OVERRIDE value is: 0
Calling as object.method:
BaseClass const char *
BaseClass const std::string &
Calling as pObject->method:
BaseClass const char *
BaseClass const std::string &
Calling as pBaseClass->method:
BaseClass const char *
BaseClass const std::string &
setProvisionalSeparateInterfaceAndImpl has exited with status 0.
//////////////////////////////////////////////
Starting new test run
OVERRIDE value is: 1
Calling as object.method:
DerivedClass const char *
BaseClass const std::string &
Calling as pObject->method:
DerivedClass const char *
BaseClass const std::string &
Calling as pBaseClass->method:
DerivedClass const char *
BaseClass const std::string &
setProvisionalSeparateInterfaceAndImpl has exited with status 0.
//////////////////////////////////////////////
Starting new test run
OVERRIDE value is: 2
Calling as object.method:
BaseClass const char *
DerivedClass const std::string &
Calling as pObject->method:
BaseClass const char *
DerivedClass const std::string &
Calling as pBaseClass->method:
BaseClass const char *
DerivedClass const std::string &
setProvisionalSeparateInterfaceAndImpl has exited with status 0.
//////////////////////////////////////////////
Starting new test run
OVERRIDE value is: 3
Calling as object.method:
DerivedClass const char *
DerivedClass const std::string &
Calling as pObject->method:
DerivedClass const char *
DerivedClass const std::string &
Calling as pBaseClass->method:
DerivedClass const char *
DerivedClass const std::string &
setProvisionalSeparateInterfaceAndImpl has exited with status 0.
I don't see that I have done anything to force polymorphic behavior in
the setProvisional methods. They just call setProvisionalImpl and pass
their argument to it. I don't really see how this differs to the
compiler from the original case. It seems that there is some magic
that happens because the non-virtual interface methods are defined by
the base class.
Sorry for the long post. I have done a fair amount of searching and
have seen things that, while similar, don't really seem to apply. I
know I can't be the first person to run into this. I would be very
grateful for an explanation or a pointer to something that explains it.
Thanks,
Rush
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]