Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphism implementation problems

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.

like image 624
S.C. Avatar asked Dec 28 '22 07:12

S.C.


1 Answers

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.

like image 137
Etienne de Martel Avatar answered Dec 29 '22 20:12

Etienne de Martel