在构造函数或析构函数中调用虚函数的行为
behaviour of virtual function called in constructor or destructor
我已经阅读了一些关于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++给出了一个更强烈的建议:永远不要在构造或销毁过程中调用虚函数
这是一个类似的问题
- 函数调用中参数的顺序重要吗
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 变量没有改变?通过向量的函数调用
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 是否有C++编译器选项允许激进地删除所有函数调用,并将参数传递给具有空体的函数
- 我知道函数调用中存在歧义.有没有办法调用foo()函数
- 模板函数调用
- 获取从C++中同一类中的构造函数调用的方法返回的值
- 析构函数调用
- 成员函数调用和C++对象模型
- 使用共享指针的函数调用,其对象应为 const
- C++:编译时检查匹配的函数调用对?
- 函数调用C++中的参数太少
- 来自 DLL 的函数调用 [表观调用的括号前面的表达式必须具有(指向-)函数类型]
- 返回指向对象的指针的函数调用是否为 prvalue?
- C++ 如何重载 [] 运算符并进行函数调用
- 代码的效率. 转到和函数调用
- 是同一作用域的函数部分中的函数调用
- 如何封装一个函数,以便它只能由同一类中的一个其他函数调用?
- 类型擦除的std::function与虚拟函数调用的开销