C#与C++ - 类型、继承和虚函数表

4
我有些困惑,不太理解是什么导致了C++和C#之间的差异。
首先,我们有一个例子,其中基类包含一个虚函数。
class Base
{
protected:
    int super;
public:
    virtual int f() = 0;
};

class Derived : public Base
{
public:
    int extraA;
    int f(){ return 1; }
};

int main()
{
    Derived *d = new Derived();

    std::vector<Base*> v;
    v.push_back(d);

    for(int i=0; i < v.size() ;i++)
    {
            // Output "Derived"
            std::cout << typeid(*v[i]).name() << std::endl;
    }

    return 0;
}

这个例子的输出是“Derived”,符合预期。
如果我们去掉f(),它就不再起作用了。输出为“Base”。例如:
class Base
{
protected:
    int super;
};

class Derived : public Base
{
public:
    int extraA;
};

int main()
{
    Derived *d = new Derived();

    std::vector<Base*> v;
    v.push_back(d);

    for(int i=0;i<v.size();i++)
    {
            // Output "Base"
            std::cout << typeid(*v[i]).name() << std::endl; 
    }

    return 0;
}

我的理解是,定义虚函数会导致编译器在对象中添加一个vptr指针,该指针指向虚函数表。虚函数表包含正确调用函数(Derived::f())的地址 - (以及对象的类型信息?)
现在来看和C#的比较。在这里,“Base”和“Derived”基本上是类似于第二个C++示例的空类:
public static void Main()
{
        Derived d = new Derived();
        IList<Base> v = new List<Base>();
        mList.Add(d);

        for (int i = 0; i < v.Count; i++)
        {
            // Output: "Derived"
            System.Console.WriteLine(v.ElementAt(i).GetType()); 
        }
}

我的问题是:我的C++理解是否正确,而C#如何在C++无法识别对象类型时正确识别对象类型?

1
这篇文章可能会帮助您了解C#中的实现原理:http://msdn.microsoft.com/en-us/magazine/cc163791.aspx --请参阅对象布局部分。 - Eric Lippert
3个回答

7
您所说的没错:只有当类具有虚函数时,C++才能实现运行时多态和类型识别,这意味着(在常见的实现中)会向类添加一个vptr(这与C++的哲学“你不需要付出不必要的代价”是一致的)。
(还包括对象的类型信息吗?)
然而,在类的vtable的第一个插槽中存储指向RTTI记录的指针是很常见的 - 我认为这是标准要求RTTI仅在类是多态时才起作用的原因之一(尽管,通常情况下这都是依赖于编译器的)。
顺便说一句,如果调用虚函数,则RTTI对于虚分派的正确工作并非必需。编译器所需做的只是使用从vtable的正确插槽中取出的指针执行call ptr操作;当通过dynamic_cast检查类层次结构以及通过typeid显式询问对象类型时,才使用RTTI记录。
相比之下,在C#中,默认情况下每个类都是多态的,并且与之相关联的是反射元数据,因此无需执行任何特殊操作即可启用多态性/类型识别。

@user1202032 这是最佳答案,在你的情况下主要是因为C#使用反射,就像Matteo Italia所说的那样。 - ForceMagic

3
在C++中,运行时类型信息只对层级结构中至少有一个虚函数的父类有效。"vtable"指针指向虚函数表并标识类型。(原则上,我不记得标准规定虚函数应该如何实现)如果没有任何虚函数,那么为了提高效率,该信息将被省略。
在C#中,类型信息始终存在,无论是否有虚函数。

2
C++和C#之间的差异是深刻而广泛的,这只是差异百科全书中的一个注释。具体来说,在C#中,每个类必须继承自Object类,它有虚函数,所以在C#中,永远不会出现对象没有虚函数的情况。然而,在C++中,通常存在这种情况。因此,在C++中,没有地方可以放置运行时类型识别信息。

2
在我看来,从object继承与这没有太大关系,他们所做的只是决定类型信息始终可用,无论是否有任何虚函数。 - Matti Virkkunen
@MattiVirkkunen:唯一可以放置该类型信息的地方是在虚函数表中。 - Puppy
@DeadMG,是什么阻止你为没有虚拟方法的类型拥有vtable呢? - svick
@svick:C++有一个规则,这样做非常浪费。 - Puppy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接