Java 在基类构造函数中调用虚方法
我知道从基类构造函数调用虚方法可能是危险的,因为子类可能不处于有效状态。(至少在C#中) 我的问题是,如果虚拟方法是初始化对象状态的方法,该怎么办?这是一种良好的做法,还是应该分两步进行,首先创建对象,然后加载状态 第一个选项:(使用构造函数初始化状态) 第二种选择:(使用两步流程) 在第一种方法中,代码使用者可以使用一条语句创建和初始化对象:Java 在基类构造函数中调用虚方法,java,c#,c++,constructor,Java,C#,C++,Constructor,我知道从基类构造函数调用虚方法可能是危险的,因为子类可能不处于有效状态。(至少在C#中) 我的问题是,如果虚拟方法是初始化对象状态的方法,该怎么办?这是一种良好的做法,还是应该分两步进行,首先创建对象,然后加载状态 第一个选项:(使用构造函数初始化状态) 第二种选择:(使用两步流程) 在第一种方法中,代码使用者可以使用一条语句创建和初始化对象: // The base class will call the virtual method to load the state. ChildObjec
// The base class will call the virtual method to load the state.
ChildObject o = new ChildObject(definition)
在第二种方法中,使用者必须创建对象,然后加载状态:
ChildObject o = new ChildObject();
o.LoadState(definition);
(这个答案适用于C++和java。我相信C++在这个问题上的工作方式不同) 在构造函数中调用虚方法确实是危险的,但有时它会以最干净的代码结束
在可能的情况下,我会尽量避免这样做,但不会严重扭曲设计。(例如,“initializelater”选项禁止不变性。)如果您确实在构造函数中使用虚方法,请强烈记录它。只要所有参与的人都知道它在做什么,就不应该造成太多问题。不过,我会尽量限制可见性,正如您在第一个示例中所做的那样 编辑:这里重要的一点是C#和Java在初始化顺序上存在差异。如果您有一个类,例如:public class Child : Parent
{
private int foo = 10;
protected override void ShowFoo()
{
Console.WriteLine(foo);
}
}
当父构造函数调用ShowFoo时,在C#中,它将显示10。java中的等效程序将显示C++中的0。,调用基类构造函数中的虚拟方法将简单地调用该方法,就好像派生类还不存在(因为它不存在)。这意味着调用在编译时被解析为它应该在基类(或它派生的类)中调用的任何方法 通过GCC测试,它允许您从构造函数调用纯虚拟函数,但它会发出警告,并导致链接时间错误。该行为似乎未被标准定义:
“成员函数可以从抽象类的构造函数(或析构函数)调用;直接或间接地对纯虚拟函数进行虚拟调用(class.virtual)对从此类构造函数(或析构函数)创建(或销毁)的对象的影响尚未定义。” < P>用C++对正在构建的类通过虚拟表路由虚拟方法。因此,在您的示例中,它将生成一个纯虚拟方法异常,因为在构造BaseObject时,根本没有LoadStateCore方法可调用 如果函数不是抽象的,而只是什么都不做,那么您经常会让程序员挠头,试图记住为什么函数实际上没有被调用
<> P>因为这个原因,在C++中,你不能这样做……< /p> < p> C++,第12.7节,第3段的标准覆盖了这个例子。 总而言之,这是合法的。它将根据正在运行的构造函数的类型解析为正确的函数。因此,将您的示例应用到C++语法中,您将调用<代码> BaseObjult::LoopStuteAudio()/<代码>。无法访问
ChildObject::LoadState()
,如果试图通过指定类和函数来访问,将导致未定义的行为
第10.4节第6段介绍了抽象类的构造函数。简言之,它们可以调用成员函数,但在构造函数中调用纯虚函数是未定义的行为。不要这样做。通常,您可以通过使用更贪婪的基本构造函数来解决这些问题。在您的示例中,将XElement传递给LoadState。如果允许在基本构造函数中直接设置状态,则子类可以在调用构造函数之前解析XElement
public abstract class BaseObject {
public BaseObject(int state1, string state2, /* blah, blah */) {
this.State1 = state1;
this.State2 = state2;
/* blah, blah */
}
}
public class ChildObject : BaseObject {
public ChildObject(XElement definition) :
base(int.Parse(definition["state1"]), definition["state2"], /* blah, blah */) {
}
}
如果子类需要做一点工作,它可以卸载到静态方法。
< P> C++,在派生构造函数之前调用基本构造函数,这意味着虚表(它包含派生类的重写虚函数的地址)还不存在。因此,这被认为是一件非常危险的事情(特别是如果函数在基类中是纯虚拟的……这将导致纯虚拟异常) 有两种解决方法:方法(2)使用工厂模式为
可访问
类提供适当的实现(它将提供与基本
类相同的接口)。这里的一个主要好处是,您可以在构建accessible
的过程中进行初始化,从而能够安全地使用accessible\u impl
的虚拟成员。如果您有一个类,如您的帖子所示,该类在构造函数中使用XElement
,那么,XElement
唯一可能来自的地方就是派生类。那么,为什么不在已经有XElement
的派生类中加载状态呢
您的示例缺少一些改变情况的基本信息,或者根本不需要使用基类的信息链接到派生类,因为它刚刚告诉您确切的信息
i、 e
<> p>你的代码非常简单,而且没有任何虚拟方法调用问题。C++中的< p>从基类中调用虚函数是完全安全的——只要它们是非纯的,就有一些限制。
public class Child : Parent
{
private int foo = 10;
protected override void ShowFoo()
{
Console.WriteLine(foo);
}
}
public abstract class BaseObject {
public BaseObject(int state1, string state2, /* blah, blah */) {
this.State1 = state1;
this.State2 = state2;
/* blah, blah */
}
}
public class ChildObject : BaseObject {
public ChildObject(XElement definition) :
base(int.Parse(definition["state1"]), definition["state2"], /* blah, blah */) {
}
}
class base
{
public:
base()
{
// only initialize base's members
}
virtual ~base()
{
// only release base's members
}
virtual bool initialize(/* whatever goes here */) = 0;
};
class derived : public base
{
public:
derived ()
{
// only initialize derived 's members
}
virtual ~derived ()
{
// only release derived 's members
}
virtual bool initialize(/* whatever goes here */)
{
// do your further initialization here
// return success/failure
}
};
class accessible
{
private:
class accessible_impl
{
protected:
accessible_impl()
{
// only initialize accessible_impl's members
}
public:
static accessible_impl* create_impl(/* params for this factory func */);
virtual ~accessible_impl()
{
// only release accessible_impl's members
}
virtual bool initialize(/* whatever goes here */) = 0;
};
accessible_impl* m_impl;
public:
accessible()
{
m_impl = accessible_impl::create_impl(/* params to determine the exact type needed */);
if (m_impl)
{
m_impl->initialize(/* ... */); // add any initialization checking you need
}
}
virtual ~accessible()
{
if (m_impl)
{
delete m_impl;
}
}
/* Other functionality of accessible, which may or may not use the impl class */
};
public class BaseClass
{
public BaseClass(XElement defintion)
{
// base class loads state here
}
}
public class DerivedClass : BaseClass
{
public DerivedClass (XElement defintion)
: base(definition)
{
// derived class loads state here
}
}
struct base {
base() { init(); }
virtual void init() = 0;
};
struct derived : base {
derived() {
// we would expect str to be "called it", but actually the
// default constructor of it initialized it to an empty string
}
virtual void init() {
// note, str not yet constructed, but we can't know this, because
// we could have called from derived's constructors body too
str = "called it";
}
private:
string str;
};