2012-01-16 5 views
4

Я написал программу, которая использует виртуальные функции для достижения полиморфизма. У меня есть основной пользовательский класс, который слепо называет метод, который, по его мнению, является общими объектами (хотя они действительно должны быть специализированными). Эти объекты относятся к классам, которые переопределяют чистые виртуальные функции в своих базовых классах. Следующий адаптированный код должен продемонстрировать свою установку:Проблемы с реализацией полиморфизма

Общий класс (BaseConfig) в BaseConfig.h:

class BaseConfig { 
public: 
    ... 
    virtual void display() const = 0; 
    ... 
} 

специализированная версия вышеуказанного общего класса (SpecialConfig) в SpecialConfig.h:

class SpecialConfig : public BaseConfig { 
public: 
    ... 
    void display() const; 
    ... 
} 

реализация вышеуказанного специализированного класса в SpecialConfig.cpp:

... 
void SpecialConfig::display() const { 
    // print some text 
} 
... 

Теперь, когда я создаю указатель BaseConfig и устанавливаю его по адресу объекта SpecialConfig, вызов display() вызывает функцию display() класса SpecialConfig, как предполагается. Тем не менее, вещи расходятся с тем, что я ожидаю в следующих фрагментах кода до такой степени, что по какой-то причине после того, как объекты SpecialConfig возвращаются в очередь BaseConfig, вызов функции display() на них больше не ударяет по функции display() в SpecialConfig но вместо этого пытается использовать функцию display() в BaseConfig, вызывая выход из программы.

Вот общий класс для генерации перестановок конфигураций. Мы будем называть его BaseRuleSet в BaseRuleSet.h:

class BaseRuleSet { 
public: 
    ... 
    virtual queue<BaseConfig *> getValidChildConfigurations(BaseConfig * c) const = 0; 
    ... 
} 

Его функция getValidChildConfigurations будет переопределен в специализированных классах Ruleset, как показано в классе SpecialRuleSet из SpecialRuleSet.h:

class SpecialRuleSet : public BaseRuleSet { 
public: 
    ... 
    queue<BaseConfig *> getValidChildConfigurations(BaseConfig * c) const; 
} 

Реализация выше класс в 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; 

} 
... 

Как показано в комментарии выше, полиморфизм по-прежнему работает правильно в этой точке, так как специализированная функция отображения все еще попадает. Однако в этом последнем фрагменте кода (показано ниже) все разваливается. Это класс пользователя из 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 


} 

Как последний комментарий показывает, в приведенном выше примере, последний вызов, чтобы отобразить() на самом деле пытается использовать чистую виртуальную функцию в BaseConfig вместо реализованной специализированной версии в SpecialConfig.

Мои мысли - либо есть ограничение, либо другой способ делать вещи на C++, о которых я не знаю, или есть ошибки в моей реализации. Может ли кто-нибудь помочь прояснить это для меня?

Спасибо.

+1

getValidChildConfigurations возвращает указатели на объекты, которые не существуют после завершения функции. – bmm6o

ответ

7

Проблема не имеет ничего общего с полиморфизмом. Ваша актуальная проблема заключается в следующем.

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. 

Вы видите, что на C++ локальная переменная живет до тех пор, пока ее область действия. Объект specialConfigA будет уничтожен, как только возвращается getValidChildConfigurations, после чего указатель, который вы сохранили в очереди, указывает на ... что-то неопределенное. Поэтому, когда вы пытаетесь вызвать метод через него, вы получаете неопределенное поведение, которое является крахом в вашем случае.

Решение динамически выделять SpecialConfig объект:

BaseConfig * baseConfigA = new SpecialConfig; 

Это означает, что объект будет уничтожен только при вызове delete. Это и хорошо, и плохо: оно больше не выходит за рамки, но вы не должны забывать использовать delete, когда все будет готово, иначе память просочится. Решением было бы использовать умный указатель, чтобы сделать для вас delete. Для этой цели C++ 11 имеет класс std::shared_ptr. Если вы все еще застряли в C++ 03, вы можете использовать boost::shared_ptr.

Если вы не можете или не хотите использовать умные указатели, то имейте в виду, что конструктор queue «s не вызывает delete на его содержание, поэтому вам придется петлю через него в одной точке и delete все.

+0

Спасибо за такой ясный и полезный ответ. –

3
SpecialConfig specialConfigA; 
SpecialConfig specialConfigB; 

baseConfigA = &specialConfigA; 
baseConfigB = &specialConfigB; 

Вы указываете объекты, которые когда-то были в стеке. Вот почему он рушится - объектов больше нет.

Смежные вопросы