我有两个这样的类(简化和重命名):
class Base
{
public:
virtual void operator()( ) { cout << "Base\n";}
};
class Derived : public Base
{
public:
void operator()( ) { cout << "Derived\n"; }
};
void doSomething( Base obj )
{
obj( );
}
int main( )
{
list<Base> bList;
Derived d1, d2, d3;
bList.push_back( d1 );
bList.push_back( d2 );
bList.push_back( d3 );
for( list<Base>::iterator it = bList.begin(); it != bList.end(); it++ )
{
doSomething( *it );
}
return 0;
}
当我跑步时,我得到:
Base
Base
Base
即。调用Base :: operator()。显然这不是我想要发生的事情。 我尝试将doSomething改为
void doSomething( Base& obj )
但这仅在使用Derived对象直接调用而不是从列表中调用时才有效。引用不能存储在列表中 有什么方法可以让doSomething“看到”它必须调用派生类的函数(不必求助于指针)?
编辑:指出了问题,因为其他可能遇到此问题的人是链接: What is object slicing?
答案 0 :(得分:7)
以下内容:
list<Base> bList;
Derived d1, d2, d3;
bList.push_back( d1 );
bList.push_back( d2 );
bList.push_back( d3 );
切片您的对象。它们不再是Derived
类型,而是Base
。您需要在容器中使用指针或智能指针来实现预期的行为。
阅读对象切片。
答案 1 :(得分:2)
问题不在于编译器没有“看到”它需要在派生类中调用函数,问题是您只是将基类存储在容器list<Base>
中。这导致通常称为对象切片的东西。当编译器将Derived
个对象复制到只能容纳Base
个对象的容器中时,它只能复制对象的基本部分,并将“切片”移出使其成为{{1}的部分。对象。
为了将多态性用于遵循值语义的容器(即,假设您将实际对象存储在它们中而不是对象的引用形式),您需要存储指向该对象的指针。我建议不要将原始指针存储在标准库容器中,因为它会引入额外的生命周期管理问题 - 您必须从容器外部控制指向对象的生命周期,这往往是一个非常糟糕的主意,通常要么导致内存泄漏或悬空指针,如果不是两者。
我的建议是,如果您可以使用boost,请使用为此目的明确设计的Derived
模板类之一。如果你不能和你使用足够现代的编译器,你可以使用ptr_container
并依靠std::list<std::shared_ptr<Base> > >
来管理对象的生命周期。
答案 2 :(得分:0)
上面的答案引导我找到最终的解决方案。简短的回答是,“不。你必须使用指针列表。”这就是我想出来的......
#include <iostream>
#include <memory>
#include <list>
class Base
{
public:
virtual void foo()
{
std::cout << "Base::foo" << std::endl;
}
};
class Derived : public Base
{
public:
void foo()
{
std::cout << "Derived::foo" << std::endl;
Base::foo();
}
};
int main()
{
std::list<std::shared_ptr<Base>> bases;
auto p = std::make_shared<Derived>();
bases.push_back(p);
for (std::list<std::shared_ptr<Base>>::iterator i = bases.begin(); i != bases.end(); i++)
{
(*i)->foo();
}
return 0;
}