在构造函数或析构函数中调用虚函数的行为
本文关键字:函数 调用 构造函数 析构函数 | 更新日期: 2023-09-27 18:13:57
我已经阅读了一些关于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); }
}
我假设您在每种情况下都创建了派生的实例。
问题的一部分是不建议在构造函数期间调用虚方法。
- 调用基构造函数。这是第一件事构造函数所做的是初始化
fun
所指向的VTable因此,当调用fun()
时,将打印 - 然后派生构造函数初始化虚函数表以覆盖
fun
为指向derived::fun
。下次调用fun()
时,它打印2
base::fun
1。MSDN强烈反对这样做:不要在构造函数中调用可重写的方法
有效的c++给出了一个更强烈的建议:永远不要在构造或销毁过程中调用虚函数
这是一个类似的问题