在基类构造函数中调用虚拟方法

2022-09-01 09:29:26

我知道从基类构造函数调用虚拟方法可能是危险的,因为子类可能不处于有效状态。(至少在 C# 中)

我的问题是,如果虚拟方法是初始化对象状态的方法,该怎么办?这是好的做法还是应该是一个两步过程,首先创建对象,然后加载状态?

第一个选项:(使用构造函数初始化状态)

public class BaseObject {
    public BaseObject(XElement definition) {
        this.LoadState(definition);
    }

    protected abstract LoadState(XElement definition);
}

第二个选项:(使用两步流程)

public class BaseObject {
    public void LoadState(XElement definition) {
        this.LoadStateCore(definition);
    }

    protected abstract LoadStateCore(XElement definition);
}

在第一种方法中,代码的使用者可以使用一个语句创建和初始化对象:

// The base class will call the virtual method to load the state.
ChildObject o = new ChildObject(definition)

在第二种方法中,使用者必须创建对象,然后加载状态:

ChildObject o = new ChildObject();
o.LoadState(definition);

答案 1

(这个答案适用于C#和Java。我相信C++在这个问题上的工作方式不同。

在构造函数中调用虚拟方法确实很危险,但有时它最终会得到最干净的代码。

我会尽可能避免它,但不要对设计进行巨大的弯曲。(例如,“稍后初始化”选项禁止不可变性。如果在构造函数中使用了虚拟方法,请非常强烈地记录它。只要每个参与者都知道它在做什么,它就不应该造成太多问题。不过,我会尝试限制可见性,就像您在第一个示例中所做的那样。

编辑:这里很重要的一件事是,C#和Java在初始化顺序上是有区别的。如果您有一个类,例如:

public class Child : Parent
{
    private int foo = 10;

    protected override void ShowFoo()
    {
        Console.WriteLine(foo);
    }
}

其中构造函数调用 ,在 C# 中它将显示 10。Java 中的等效程序将显示 0。ParentShowFoo


答案 2

在C++中,在基类构造函数中调用虚拟方法将简单地调用该方法,就好像派生类尚不存在一样(因为它不存在)。因此,这意味着调用在编译时解析为它应该在基类(或它派生的类)中调用的任何方法。

使用GCC进行测试,它允许您从构造函数调用纯虚函数,但它会发出警告,并导致链接时间错误。此行为似乎未由标准定义:

“成员函数可以从抽象类的构造函数(或析构函数)调用;对从此类构造函数(或析构函数)创建(或销毁)的对象直接或间接进行虚拟调用(class.virtual)的效果是未定义的。