Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/331.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在基类构造函数中调用虚方法_Java_C#_C++_Constructor - Fatal编程技术网

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

Java 在基类构造函数中调用虚方法,java,c#,c++,constructor,Java,C#,C++,Constructor,我知道从基类构造函数调用虚方法可能是危险的,因为子类可能不处于有效状态。(至少在C#中) 我的问题是,如果虚拟方法是初始化对象状态的方法,该怎么办?这是一种良好的做法,还是应该分两步进行,首先创建对象,然后加载状态 第一个选项:(使用构造函数初始化状态) 第二种选择:(使用两步流程) 在第一种方法中,代码使用者可以使用一条语句创建和初始化对象: // The base class will call the virtual method to load the state. ChildObjec

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

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

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

第二种选择:(使用两步流程)

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

// 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++,在派生构造函数之前调用基本构造函数,这意味着虚表(它包含派生类的重写虚函数的地址)还不存在。因此,这被认为是一件非常危险的事情(特别是如果函数在基类中是纯虚拟的……这将导致纯虚拟异常)

有两种解决方法:

  • 执行构造+初始化的两步流程
  • 将虚拟函数移动到您可以更紧密控制的内部类(可以使用上述方法,请参见示例了解详细信息)
  • (1)的一个例子是:

    (2)的一个例子是:


    方法(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;
    };