I have written a program which uses virtual functions to achieve polymorphism. I have a main User class which blindly calls a method on what it thinks are generic objects (although they should actually be specialized). These objects are from classes that override pure virtual functions in their base classes. The following adapted code should demonstrate my setup:
A generic class (BaseConfig) in BaseConfig.h:
class BaseConfig {
public:
...
virtual void display() const = 0;
...
}
A specialized version of the above generic class (SpecialConfig) in SpecialConfig.h:
class SpecialConfig : public BaseConfig {
public:
...
void display() const;
...
}
The implementation of the above specialized class in SpecialConfig.cpp:
...
void SpecialConfig::display() const {
// print some text
}
...
Now when I create BaseConfig pointer and set it to the address of a SpecialConfig object, calling display() hits the display() function of the SpecialConfig class as it is supposed to. However, things diverge from what I would expect in the following code snippets to the point where for some reason, after SpecialConfig objects are returned in a BaseConfig queue, calling the display() function on them no longer hits the display() function in SpecialConfig but instead attempts to use the display() function in BaseConfig, causing a program exit.
Here is a generic class for generating permutations of configurations. We'll call it BaseRuleSet in BaseRuleSet.h:
class BaseRuleSet {
public:
...
virtual queue<BaseConfig *> getValidChildConfigurations(BaseConfig * c) const = 0;
...
}
Its getValidChildConfigurations function will be overridden in the specialized RuleSet classes as shown in the class SpecialRuleSet from SpecialRuleSet.h:
class SpecialRuleSet : public BaseRuleSet {
public:
...
queue<BaseConfig *> getValidChildConfigurations(BaseConfig * c) const;
}
The implementation of the above class in SpecialRuleSet.cpp:
...
queue<BaseConfig *> SpecialRuleSet::getValidChildConfigurations(BaseConfig * c) const {
queue<BaseConfig *> validChildConfigurations;
BaseConfig * baseConfigA;
BaseConfig * baseConfigB;
SpecialConfig specialConfigA;
SpecialConfig specialConfigB;
baseConfigA = &specialConfigA;
baseConfigB = &specialConfigB;
validChildConfigurations.push(baseConfigA);
validChildConfigurations.push(baseConfigB);
// validChildConfigurations.front()->display() works correctly here
return validChildConfigurations;
}
...
As shown in the comment above, the polymorphism is still working correctly at this point since the specialized display function is still being hit. However, in this last code snippet (shown below), everything falls apart. This is the User class from User.cpp:
...
void User::doStuff() {
BaseRuleSet * baseRuleSet;
SpecialRuleSet specialRuleSet;
baseRuleSet = &specialRuleSet;
BaseConfig * currentConfig;
/*
SpecialConfig specialConfig;
currentConfig = &specialConfig;
currentConfig->display(); // this works
*/
queue<BaseConfig *> childConfigurations = ruleSet->getValidChildConfigurations(currentConfig);
childConfigurations.front()->display(); // this does not work
}
As the last comment shows in the above example, the last call to display() actually tries to use the pure virtual function in BaseConfig instead of the implemented specialized version in SpecialConfig.
My thoughts are either there is a limitation or different way of doing things in C++ that I am not aware of or there are errors in my implementation. Can anyone help clarify this for me?
Thanks.
The problem has nothing to do with polymorphism. Your actual issue is the following.
BaseConfig * baseConfigA; // Okay.
SpecialConfig specialConfigA; // Fair enough
baseConfigA = &specialConfigA; // Hmm, getting the address of a local variable?
validChildConfigurations.push(baseConfigA); // This should be okay as long as
// you don't return that queue...
return validChildConfigurations; // Oh dear.
You see, in C++, a local variable lives as long as its scope. The specialConfigA
object above will be destroyed as soon as getValidChildConfigurations
returns, after which the pointer that you stored in the queue points to... something undefined. So when you attempt to call a method through it, you get undefined behavior, which is a crash in your case.
The solution is to dynamically allocate the SpecialConfig
object:
BaseConfig * baseConfigA = new SpecialConfig;
That means the object will only be destroyed when you invoke delete
. That's both a good and a bad thing: it will no longer go out of scope, but you must not forget to use delete
when you're done, otherwise the memory will leak. A solution would be to use a smart pointer to do the delete
for you. C++11 has the std::shared_ptr
class for that purpose. If you're still stuck in C++03, you can use boost::shared_ptr
.
If you can't or don't want to use smart pointers, then keep in mind that queue
's constructor does not invoke delete
on its content, so you'll have to loop through it at one point and delete
everything.
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