Изменение класса на его дочерний класс - PullRequest
3 голосов
/ 17 ноября 2011

Я пытаюсь сделать так, чтобы каждый экземпляр класса (именуемый здесь Caller) имел экземпляр другого класса (Target). Дело в том, что во втором классе много детей, и мне нужно иметь возможность переключать класс Caller среди них по желанию. Я пробовал несколько способов, но ни один не дал мне желаемых результатов. Текущий код:

class Target
{
public:
    virtual void do_something()
    { log_message("NO!"); }
};

class TargetChild : public Target
{
public:
    virtual void do_something()
    { log_message("YES!"); }
};

class Caller
{
private:
    Target target;

public:
    void call_target()
    { target.do_something(); }
    void set_target(Target set_target)
    { target = set_target; }
};

int main( int argc, const char* argv[] )
{
    TargetChild targetChild;

    Caller caller;
    caller.call_target();
    caller.set_target(targetChild);
    caller.call_target();
}

Требуемый результат в файле журнала "НЕТ! ДА!" но вместо этого пишет НЕТ! дважды. Я не могу понять, что с ним не так.

Ответы [ 5 ]

6 голосов
/ 17 ноября 2011

Вы не можете изменить тип объекта в C ++.Вы можете только создавать, уничтожать, копировать и (в C ++ 11) перемещать данные.В этом случае вы скопировали данные из объекта подкласса в объект базового класса.Это скопировало пустое подмножество пустого подмножества.Проблема, которую вы наблюдали, называется «нарезка».

Вероятно, вам нужен член, содержащий указатель на функцию, который можно изменить.virtual возвращает указатель на функцию, но его нельзя изменить.Попробуйте std::tr1::function, boost::function или C ++ 11 std::function.

Или, если вы действительно хотите идти по маршруту virtual, используйте указатель Target *.Лучше всего использовать класс интеллектуальных указателей, например unique_ptr< Target > (снова Boost / TR1 / C ++ 11) или std::auto_ptr< Target > (старомодный C ++ 03).Вы также можете сделать это самостоятельно с помощью new и delete, но такой код на самом деле не работает, а подходит только для небольшого обучения.

1 голос
/ 17 ноября 2011

Ваш код страдает от обрезки объектов . Даже если targetChild в main является TargetChild, оно передается по значению в Caller::set_target, что означает, что оно копируется в локальную переменную типа Target. Чтобы обойти срезы объектов в целом, вы должны использовать передачу по ссылке или указатели.

В этом случае, поскольку вы хотите, чтобы Caller получил доступ к переданному объекту вне метода Caller::set_target, вам следует использовать указатель. Только ссылки не будут работать, потому что, хотя вы можете сделать ссылку Caller::target, вы не можете изменить объект, на который она ссылается.

Указатели приводят к проблемам с управлением памятью, так как вы должны убедиться, что TargetChild освобожден (в противном случае программа имеет утечку памяти), но не слишком скоро (что приведет к нарушениям доступа, скорее всего, к аварийному завершению вашей программы). В библиотеке boost есть различные классы smart pointer , чтобы упростить эту задачу. Класс C ++ auto_ptr также может быть использован, если только один другой класс должен владеть экземпляром TargetChild в любой момент времени.

0 голосов
/ 17 ноября 2011

Используйте интерфейс, ITarget и переключайте их по желанию ITarget

virtual void do_something()     { log_message("NO!"); } 

Относитесь ко всему как к типу ИТ-цели

Это аккуратный способ справиться с этим.

Мой с ++ ржавый:)

0 голосов
/ 17 ноября 2011

В set_target вы передаете targetChild по значению, там вы теряете шанс вызвать TargetChild :: do_something. Вы должны расширить вызывающую функцию, чтобы включить ссылку (или указатель) на текущую цель, ссылка (или указатель) сохранит информацию об исходном TargetChild, а затем компилятор по-прежнему будет вызывать TargetChild :: do_something.

0 голосов
/ 17 ноября 2011

Вместо этого используйте указатели.Подробнее о полиморфизме: http://www.cplusplus.com/doc/tutorial/polymorphism/

...
class Caller
{
    private:
        Target* target;

    public:
        Caller() { target = NULL; }
        void call_target() { if (target != NULL) target->do_something(); }
        void set_target(Target* set_target) { target = set_target; }
};

int main( int argc, const char* argv[] )
{
    ....
    caller.set_target(&targetChild);
    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...