在构造函数或析构函数中调用虚函数的行为

behaviour of virtual function called in constructor or destructor

本文关键字:函数 调用 构造函数 析构函数      更新时间:2023-10-16

我已经阅读了一些关于c++和c#在构造函数或析构函数中调用虚函数的不同行为的资料。然后测试下面的代码,以确认c#可以调用虚派生虚函数,因为它的对象存在于构造函数之前。然而,我发现结果与c++中的类似代码相同。谁能告诉我为什么c#不能显示"22",只能显示"12"?

c#代码
public class Base
{
    public Base() { fun(); }
    public virtual void fun() { Console.WriteLine(1); }
}
public class Derived : Base
{
    public Derived() { fun(); }
    public virtual void fun() { Console.WriteLine(2); }
}

c++代码
class Base
{
public:
    Base(){fun();}
    virtual void fun(){cout<<1;}
};
class Derived : Base
{
public:
    Derived(){fun();}
    virtual void fun(){cout<<2;}
};

c++和c#的输出结果都是"12"

你的c#代码有一个错误。

要覆盖c#函数,必须指定override关键字。如果你再次写virtual,你是在遮蔽基本函数,但没有new关键字,你应该得到一个警告。

如果两个函数都声明为虚函数,则有两个函数具有相同的名称!

让我们做一个例子…

public class Base
{
    public Base() { fun(); }
    public virtual void fun() { Console.Write(1); }
}
public class Derived : Base
{
    public Derived() { fun(); }
    public override void fun() { Console.Write(2); }
}
public class DerivedWithError : Base
{
    public DerivedWithError() { fun(); }
    public new virtual void fun() { Console.Write(3); }
}
...
// This will print "22".
Base normal = new Derived();
Console.WriteLine();
// This will print "13" !!!
Base withError = new DerivedWithError ();
Console.WriteLine();
// Let's call the methods and see what happens!
// This will print "2"
normal.fun();
Console.WriteLine();
// This will print "1" !!!
withError.fun();
Console.WriteLine(); 

遮蔽意味着"在不使用多态性的情况下添加同名的新方法"。如果没有override关键字,您将禁用多态性。

所以现在所有的东西都应该是干净的,容易理解的。

DerivedWithError.fun()是一个全新的虚拟方法。它与基类中的函数名称相同,但它们并不相关!

从虚表(或者虚方法表,如果您喜欢另一个名称)的角度来看,基函数和派生函数在虚表中占据两个不同的表项,即使它们具有相同的名称。如果使用override,则强制派生类中的func方法覆盖Base占用的虚拟表项。函数,这就是多态性。

是危险的,但在。net中是允许的,一定要小心语法!

你可以在c#的构造函数中调用虚函数,但是通常在派生类中,如果一个方法是在基构造函数中调用的,你应该小心如何在派生类中使用字段。现在,为了简洁,避免混淆和风险,您应该避免在构造函数中调用虚拟方法,但实际上它在很多时候是非常有用的。

例如不能访问任何变量的所谓"工厂方法"。

public abstract class MyClass
{
    public IList<string> MyList { get; private set; }
    public MyClass()
    {
         this.MyList = this.CreateMyList();
    }
    protected abstract IList<string> CreateMyList();
}
public class MyDerived : MyClass
{
    protected override IList<string> CreateMyList()
    {
        return new ObservableCollection<string>();
    }
}

这是完全合法的,它工作!

问题是,即使函数具有相同的名称" fun() ",每个函数在其声明中都是类的成员。所以当你从基类调用fun()时,你调用的是Base.func(),当你从派生类调用fun()时,它就像Derived.fun();如果你想输出"22",你需要在Derived类中覆盖fun()

     public class Derived : Base
    {
        public Derived() { fun(); }
        public override void fun() { Console.WriteLine(2); }
    }

我假设您在每种情况下都创建了派生的实例。

问题的一部分是不建议在构造函数期间调用虚方法。

  1. 调用基构造函数。这是第一件事构造函数所做的是初始化fun所指向的VTable因此,当调用fun()
  2. 时,将打印base::fun 1。
  3. 然后派生构造函数初始化虚函数表以覆盖fun为指向derived::fun。下次调用fun()时,它打印2

MSDN强烈反对这样做:不要在构造函数中调用可重写的方法

有效的c++给出了一个更强烈的建议:永远不要在构造或销毁过程中调用虚函数

这是一个类似的问题