我是C ++的新手,我来自Java / C#。
我知道在Java和C#中你可以创建一个类,让另一个类继承它,并覆盖它的功能。然后,您可以创建父类的列表,并将子类的对象插入此列表中。之后,您可以使用被覆盖的功能。
示例:
public class Parent
{
public virtual void test()
{
Console.WriteLine("test");
}
}
public class Child : Parent
{
public override void test()
{
Console.WriteLine("test2");
}
}
用法:
List<Parent> tests = new List<Parent>();
tests.Add(new Child());
tests[0].test();
输出:
test2
在C ++中, 当我使用std::vector
执行此操作时,它会调用父级的成员函数,而不是子级函数。
如何在C ++中完成上述操作?
答案 0 :(得分:8)
我觉得这里有两个问题。一个是语法问题,其他人已经解决了。但是,您似乎还有一个潜在的问题: 尝试在C ++中编写Java / C#代码 。无论语法问题是什么,这都会导致痛苦,所以我试着在这里解决这个问题。
在c ++中,当我用vector执行此操作时,它会调用父项的函数。我怎样才能在C ++中做上面的例子?
Java和C#使用面向对象的范例 。 C ++的不同之处在于 C ++是一种多范式语言 。它支持(或多或少)结构化,面向对象,通用,功能和诸如编程范例。您可以自由地 混合和混合范例 ,而C ++在您这样做的地方闪耀着光芒。
源自STL的标准库部分,即:容器,算法,迭代器,根本不是OO。他们正在应用 通用编程 。其中一个属性是容器通常(有异常,但不在标准库本身内)存储值,而不是引用。然而,多态性,至少运行时多态性,仅对引用(或语法上,指针,也是语义上的引用)进行操作。
如果您有std::vector<base_class> vc
,这将存储实际的值,而不是对堆上某处对象的引用。如果将对象放入此类容器中,该对象实际上将复制到容器中。如果您输入derived_class
对象,则需要 切片 。也就是说,只有base_class
部分被复制到容器中,所有derived_class
部分都将被忽略。然后,您最终在容器中使用实际的base_class
对象,而不是像在Java和C#中那样在堆上某处派生类对象的基类引用。
这就是为什么在该对象上调用成员函数将最终在基类中: 没有派生类对象来调用 上的函数。
在C ++中, 如果你想使用OOP ,你通常必须 动态分配派生类对象 (即new derived_class()
)并将它们分配给基类指针。这个问题是C ++没有垃圾收集,所以你必须跟踪那些指针,以及从中生成的所有副本,并在最后一个指针被销毁之前显式删除对象。那是 非常容易出错的 ,这就是为什么现在每个人都让 智能指针 自动执行此操作。
所以你想要的是std::vector<smart_ptr<base_class>>
并放入new derived_class()
个对象。符号smart_ptr
所指的内容取决于您的需求。如果您计划存储指向那些对象的指针,但在该容器中,std::unique_ptr
(std::tr1::unique_ptr
如果您的编译器仅支持C ++ 03,或boost::unique_ptr
如果它甚至不支持)是理想的。如果您可以自由地传递这些指针,并且它们可以跟踪最后一次超出自己的范围,std::shared_ptr
会更好。
现在,所有这些都说,我觉得有必要添加: 您可能根本不需要以OO方式执行此操作 。如果您可以放弃严格的OO思维Java和C#监禁你,可能会有更好的设计。
如果你使用多态,那么你可以将具有不同内容的容器传递给相同的算法,那么使用 通用编程可能会好得多 :
template<typename FwdIt>
void do_something(FwdIt begin, FwdIt end)
{
while(begin != end)
if(begin->foo() == bar()) // whatever
begin->baz(); // whatever
}
std::vector<some_class> vs;
std::vector<other_class> vo;
std::deque<other_class> do;
// ...
do_something(vs.begin(), vs.end());
do_something(vo.begin(), vo.end());
do_something(do.begin(), do.end());
这适用于所有类型(此处为some_class
),其中foo()
成员不接受任何参数并返回与bar()
返回的内容相当的内容,并且具有baz()
成员,也没有任何参数。 (如果你尝试使用某些没有那些类型的类型,编译器会咆哮你。)
答案 1 :(得分:3)
与Java或C#不同,C ++默认使用值语义。一个
std::vector<Parent>
包含Parent
类型的实际对象,而不包含Parent
类型的实际对象
指针或引用。当你插入向量时,对象就是你
正在插入被复制,并被复制到std::vector<ValueType> v;
v.push_back( ValueType() ); // no new
的对象中
类型。 (对象不能更改类型。)这称为切片。
如果要在C ++中使用多态,则必须指定 明确地说你想要引用语义。指针和 引用提供了引用语义,并且可以定义 “智能指针” - 类似于指向其他指针的类 类。由于引用不支持复制/赋值语义 标准容器所要求的,它们不能用于实例化 容器,所以如果容器要容纳多态对象,那就必须 被定义为包含指针。所以:
std::vector<BaseType*> v;
v.push_back( new DerivedType() ); // dynamic allocation.
但
class Parent
{
public:
virtual ~Parent() {}
// ...
};
由于切片,多态性和复制/赋值不能很好地工作 在一起,通常在设计的类中阻止复制/分配 成为等级制的基础。
此外,如果您要通过指向基础的指针来管理对象 类,析构函数应该是虚拟的:
{{1}}
否则,当你去删除时,你会遇到未定义的行为 对象(通过指向其基础的指针)。
答案 2 :(得分:1)
test()
应在virtual
课程中Parent
,以确保调用Child
班级test()
。
答案 3 :(得分:1)
看起来boost::ptr_container
库对您非常有帮助。
它的工作方式与(智能)指针的向量相同,但它具有设计用于此类的语法的额外好处。
例如,您可以执行以下操作:
typedef boost::ptr_vector<AbstractClass> PolyVector;
PolyVector polyVect;
polyVect.push_back( std::unique_ptr( new ChildClassA() ) );
polyVect.push_back( std::unique_ptr( new ChildClassB() ) );
polyVect.push_back( std::unique_ptr( new ChildClassC() ) );
BOOST_FOREACH( PolyVector::value_type item, polyVect)
item.memberFunction( x );
这将调用虚拟memberFunction
的派生类实现。
答案 4 :(得分:0)
C ++没有override
个关键字。只需将重写的方法重新声明为virtual
。
答案 5 :(得分:0)
在C ++中,这看起来如下所示:
MyList<Parent*>* tests = new MyList<Parent*>();
tests->Add(new Child());
tests->test();
为了在C ++中调用多态函数,您将调用子函数而不是父函数,您必须使用指向或引用父类的指针或引用,并且类方法本身需要声明为{父类和子类声明中的{1}}。
请记住,如果不补偿virtual
对象“拥有”(或应该拥有)传递给它的指针这一事实,那么使用这样的原始指针可能会导致严重的内存泄漏。如果所有权含糊不清,则需要格外小心,或使用MyList
之类的内容。例如,如果您决定使用带有原始指针的std::shared_ptr<T>
之类的STL容器,那么容器将不会“拥有”分配给每个指针的内存,并且当容器被销毁时,它将不会释放内存被每个成员指出,导致内存泄漏。
例如,这是一个working example多态行为:
std::vector