将类更改为其子类

时间:2011-11-17 12:20:07

标签: c++ polymorphism

我正在尝试使类的每个实例(此处名为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();
}

日志文件中的结果是“NO!YES!”但它写了NO!两次。我真的不明白它有什么问题。

5 个答案:

答案 0 :(得分:6)

您无法在C ++中更改对象的类型。您只能创建,销毁,复制和(在C ++ 11中)移动数据。在这种情况下,您已将子类对象中的数据复制到基类对象。这复制了空子集的空子集。您观察到的问题称为"切片。"

你想要的是一个包含可以改变的函数指针的成员。 virtual为您提供一个函数指针,但无法更改。试试std::tr1::functionboost::function或C ++ 11 std::function

或者,如果您真的想要使用virtual路线,请使用Target *指针。最好使用智能指针类,例如unique_ptr< Target >(再次是Boost / TR1 / C ++ 11)或std::auto_ptr< Target >(老式的C ++ 03)。您也可以使用newdelete自行完成,但这些代码并不能真正发挥作用,只适合进行一些教育修补。

答案 1 :(得分:1)

您的代码遇到object slicing。即使targetChild中的mainTargetChild,它也会按值传递给Caller::set_target,这意味着它会被复制到类型为Target的局部变量。要解决一般的对象切片,必须使用pass-by-reference或使用指针。

在这种情况下,由于您希望Caller访问Caller::set_target方法之外的传递对象,因此您应该使用指针。单独引用不起作用,因为虽然您可以使Caller::target成为引用,但您无法更改它引用的对象。

指针引入了内存管理问题,因为您必须确保TargetChild已取消分配(否则程序会发生内存泄漏),但不会太快(这会导致访问冲突,最有可能导致程序崩溃)。 boost库有各种smart pointer类,可以使这更容易。如果在任何一个时间点只有一个其他类应该拥有TargetChild实例,那么也可以使用C ++标准auto_ptr类。

答案 2 :(得分:0)

使用指针代替。有关多态性的更多信息: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);
    ...
}

答案 3 :(得分:0)

在set_target中,您按值传递targetChild,在那里您没有机会调用TargetChild :: do_something。您需要扩展调用者以包含对当前目标的引用(或指针),引用(或指针)将保留有关原始TargetChild的信息,然后编译器仍将调用TargetChild :: do_something。

答案 4 :(得分:0)

使用界面,ITarget并随意切换它们     ITarget

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

将所有内容视为ITarget类型

这是一种处理它的巧妙方法。

我的c ++生锈了:)