3

我对 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++ 中完成上述操作?

4

6 回答 6

8

我觉得你这里有两个问题。一个是句法问题,其他人已经解决了这个问题。但是,您似乎还有一个潜在的问题,即尝试用 C++ 编写 Java/C# 代码。无论语法问题是什么,这都会导致痛苦,所以我在这里尝试解决这个问题。

在 C++ 中,当我使用向量执行此操作时,它会调用该父函数。如何在 C++ 中执行上述示例?

Java 和 C# 对一切都使用面向对象的范例。C++ 的不同之处在于C++ 是一种多范式语言。它支持(或多或少)结构化、OO、通用、功能和诸如此类的编程范式。你可以自由地混合和混合范式,而 C++ 在你这样做的地方最闪耀。

标准库中从 STL 派生的部分,即:容器、算法、迭代器,根本不是 OO。他们正在应用通用编程。其属性之一是容器通常(有例外,但不在标准库本身内)存储,而不是引用。然而,多态性,至少是运行时多态性,只对引用进行操作(或者,在语法上,指针,在语义上也是引用)。

如果你有 a 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_ptrstd::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()成员,也不接受任何参数。(如果你尝试使用一些没有这些的类型,编译器会向你咆哮。)

于 2012-06-25T12:51:52.607 回答
3

与 Java 或 C# 不同,C++ 默认使用值语义。An std::vector<Parent>包含Parent类型的实际对象,而不是指针或引用。当您插入向量时,您正在插入的对象被复制,并且它被复制到Parent 类型的对象中。(对象不能更改类型。)这称为切片。

如果要在 C++ 中使用多态性,则必须明确指定要使用引用语义。指针和引用都提供引用语义,并且可以定义“智能指针”——行为类似于指向其他类的指针的类。由于引用不支持标准容器所需的复制/赋值语义,因此它们不能用于实例化容器,因此如果容器要保存多态对象,则必须将其定义为包含指针。所以:

std::vector<ValueType> v;
v.push_back( ValueType() );         //  no new

std::vector<BaseType*> v;
v.push_back( new DerivedType() );   //  dynamic allocation.

由于切片,多态性和复制/分配不能很好地协同工作,并且通常在设计为层次结构基础的类中阻止复制/分配。

此外,如果您要通过指向基类的指针来管理对象,则析构函数应该是虚拟的:

class Parent
{
public:
    virtual ~Parent() {}
    //  ...
};

否则,当您删除对象时(通过指向其基的指针),您将遇到未定义的行为。

于 2012-06-25T13:11:13.787 回答
1

test()应该virtual在您的Parent类中进行,以确保正在调用Child该类。test()

于 2012-06-25T12:13:22.807 回答
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 );

这将调用 virtual 的派生类实现memberFunction

于 2012-06-25T13:14:58.340 回答
0

C++ 没有override关键字。只需将您覆盖的方法重新声明为virtual.

于 2012-06-25T12:02:00.577 回答
0

在 C++ 中,这将类似于以下内容:

MyList<Parent*>* tests = new MyList<Parent*>();
tests->Add(new Child());
tests->test();

为了在 C++ 中调用多态功能,您将调用子函数而不是父函数,您必须使用指向或引用父类的指针或引用,并且类方法本身需要在父类中声明,virtual并且子类声明。

请记住,如果您不补偿MyList对象“拥有”(或应该拥有)传递给它的指针这一事实,那么使用这样的原始指针可能会导致一些严重的内存泄漏。如果所有权不明确,您需要格外小心,或使用类似std::shared_ptr<T>. 例如,如果您决定使用std::vector带有原始指针的 STL 容器,那么容器将不会“拥有”分配给每个指针的内存,并且当容器被销毁时,它不会释放每个指针指向的内存它的成员,导致严重的内存泄漏。

顺便说一句,这是关于 C++ 的一个非常重要的点……与 C#/Java 不同,C++ 使用显式指针,而不是隐式指针。因此,如果您声明一个对象,使其不是指针(即,它是堆栈上的静态或“自动”变量),那么如果您将派生类实例对象复制到父实例中,您将最终“切片" 关闭派生对象的父部分,只复制派生对象的父部分。那不是你想要的。您需要多态行为,因此您必须使用指向父类类型的指针或引用。

例如,这是一个多态行为的工作示例

#include <iostream>

//polymorphic base
struct test
{
    virtual void print() { std::cout << "I'm the parent" << std::endl; }
};

//derived type
struct derived : public test
{
    virtual void print() { std::cout << "I'm the derived" << std::endl; }
};

int main()
{
    test* a = new test;
    test* b = new derived;

    a->print();
    b->print();  //calls derived::print through polymorphic behavior

    return 0;
}
于 2012-06-25T12:02:12.450 回答