【问题标题】:behaviour of virtual function called in constructor or destructor在构造函数或析构函数中调用的虚函数的行为
【发布时间】:2011-12-24 22:02:25
【问题描述】:

我已经阅读了一些关于在 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# 2.0 标准中阅读了关于这个主题的注释。我稍后会挖掘它。您使用的是哪个 C# 版本?
  • C# 代码中确实存在错误,如答案中所述。

标签: c# c++ constructor virtual


【解决方案1】:

问题在于,即使函数具有相同的名称“fun()”,每个函数在其声明的内容中都是类的成员。因此,当您从基类 ctor 调用 fun() 时,您正在调用 Base.func(),当您从派生类调用 fun() 时,它就像 Derived.fun();如果要输出“22”,则需要在 Derived 类中覆盖 fun()

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

【讨论】:

  • @DaveHillier:OP 已经知道不能从 C++ 中的基类型调用虚函数。
  • @Dave Hillier 问题是“谁能告诉我为什么 c# 不能显示“22”而只能显示“12””
  • 天真的问题:在 C# 中是否需要 virtual override
  • 你发现了一个错误——但这不是他问题的答案。
  • 戴夫你读过这个问题吗?如果是,请再读一遍。
【解决方案2】:

我假设您在每种情况下都创建了一个 derived 实例。

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

  1. 调用基本构造函数。部分第一件事 构造函数所做的是初始化 VTable fun 指向 base::fun,因此,当您调用fun() 时会打印1
  2. 然后 派生构造函数初始化 vtable 以覆盖 fun 到 指向derived::fun。下次您拨打 fun() 时,它会打印 2

MSDN 强烈建议不要这样做:Do not call overridable methods in constructors

Effective C++ 提出甚至更强的建议:Never Call Virtual Functions during Construction or Destruction

这是一个类似的question

【讨论】:

  • OP 已描述他已经知道在 C++ 中基本构造函数不调用派生虚函数。你误解了这个问题。他的问题是,当他被告知 C# 与 C++ 不同时,为什么 C# 会那样做?
【解决方案3】:

您的 C# 代码中有错误。

要覆盖 C# 函数,您必须指定 override 关键字。 如果您再次编写 virtual,您将隐藏基本函数,但如果没有 new 关键字,您应该会收到警告。

如果两者都被声明为 virtual,那么你就有两个同名的函数!

我们举个例子……

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(); 

Shadowing 的意思是“在不使用多态的情况下添加一个具有相同名称的新方法”。 如果没有 override 关键字,您将禁用多态性。

所以现在一切都应该清晰易懂。

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

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

危险但 .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>();
    }
}

这是完全合法的,而且有效!

【讨论】:

    猜你喜欢
    • 2013-09-06
    • 1970-01-01
    • 2011-04-16
    • 2021-10-20
    • 2012-01-28
    • 2010-10-05
    • 1970-01-01
    • 2012-01-28
    相关资源
    最近更新 更多