C# 在基类构造函数中调用虚方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/448258/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Calling virtual method in base class constructor
提问by Yona
I know that calling a virtual method from a base class constructor can be dangerous since the child class might not be in a valid state. (at least in C#)
我知道从基类构造函数调用虚拟方法可能很危险,因为子类可能不在有效状态。(至少在 C# 中)
My question is what if the virtual method is the one who initializes the state of the object ? Is it good practice or should it be a two step process, first to create the object and then to load the state ?
我的问题是,如果虚方法是初始化对象状态的方法呢?这是好的做法还是应该分两步走,首先创建对象,然后加载状态?
First option: (using the constructor to initialize the state)
第一个选项:(使用构造函数初始化状态)
public class BaseObject {
public BaseObject(XElement definition) {
this.LoadState(definition);
}
protected abstract LoadState(XElement definition);
}
Second option: (using a two step process)
第二种选择:(使用两步过程)
public class BaseObject {
public void LoadState(XElement definition) {
this.LoadStateCore(definition);
}
protected abstract LoadStateCore(XElement definition);
}
In the first method the consumer of the code can create and initialize the object with one statement:
在第一种方法中,代码的使用者可以使用一条语句创建和初始化对象:
// The base class will call the virtual method to load the state.
ChildObject o = new ChildObject(definition)
In the second method the consumer will have to create the object and then load the state:
在第二种方法中,消费者必须创建对象,然后加载状态:
ChildObject o = new ChildObject();
o.LoadState(definition);
采纳答案by Jon Skeet
(This answer applies to C# and Java. I believe C++ works differently on this matter.)
(这个答案适用于 C# 和 Java。我相信 C++ 在这个问题上的工作方式不同。)
Calling a virtual method in a constructor is indeed dangerous, but sometimes it can end up with the cleanest code.
在构造函数中调用虚方法确实很危险,但有时它可以得到最干净的代码。
I would try to avoid it where possible, but without bending the design hugely. (For instance, the "initialize later" option prohibits immutability.) If you douse a virtual method in the constructor, document it verystrongly. So long as everyone involved is aware of what it's doing, it shouldn't cause toomany problems. I would try to limit the visibility though, as you've done in your first example.
我会尽量避免它,但不会极大地弯曲设计。(例如,“稍后初始化”选项禁止不变性。)如果您确实在构造函数中使用了虚拟方法,请非常强烈地记录它。只要涉及的每个人都知道它在做什么,就不应该造成太多问题。不过,正如您在第一个示例中所做的那样,我会尝试限制可见性。
EDIT: One thing which is important here is that there's a difference between C# and Java in order of initialization. If you have a class such as:
编辑:这里很重要的一件事是 C# 和 Java 之间在初始化顺序上存在差异。如果你有一个类,例如:
public class Child : Parent
{
private int foo = 10;
protected override void ShowFoo()
{
Console.WriteLine(foo);
}
}
where the Parent
constructor calls ShowFoo
, in C# it will display 10. The equivalent program in Java would display 0.
在Parent
构造函数调用的地方ShowFoo
,在 C# 中它将显示 10。Java 中的等效程序将显示 0。
回答by Greg Rogers
In C++, calling a virtual method in a base class constructor will simply call the method as if the derived class doesn't exist yet (because it doesn't). So that means that the call is resolved at compile time to whatever method it should call in the base class (or classes it derived from).
在 C++ 中,在基类构造函数中调用虚拟方法只会调用该方法,就好像派生类尚不存在一样(因为它不存在)。所以这意味着调用在编译时被解析为它应该在基类(或它派生的类)中调用的任何方法。
Tested with GCC, it allows you to call a pure virtual function from a constructor, but it gives a warning, and results in a link time error. It appears that this behavior is undefined by the standard:
通过 GCC 测试,它允许您从构造函数调用纯虚函数,但它会发出警告,并导致链接时间错误。标准似乎未定义此行为:
"Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (class.virtual) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined."
“可以从抽象类的构造函数(或析构函数)调用成员函数;对于从这样的对象创建(或销毁)的对象,直接或间接对纯虚函数进行虚调用(class.virtual)的效果构造函数(或析构函数)未定义。”
回答by Rob Walker
With C++ the virtual methods are routed through the vtable for the class that is being constructed. So in your example it would generate a pure virtual method exception since whilst BaseObject is being constructed there simply is no LoadStateCore method to invoke.
使用 C++,虚拟方法通过正在构造的类的 vtable 路由。因此,在您的示例中,它将生成纯虚拟方法异常,因为在构造 BaseObject 时,根本没有要调用的 LoadStateCore 方法。
If the function is not abstract, but simply does nothing then you will often get the programmer scratching their head for a while trying to remember why it is that the function doesn't actually get called.
如果函数不是抽象的,而只是什么都不做,那么你经常会让程序员挠头,试图记住为什么函数实际上没有被调用。
For this reason you simply can't do it this way in C++ ...
出于这个原因,你根本不能在 C++ 中这样做......
回答by David Thornley
For C++, section 12.7, paragraph 3 of the Standard covers this case.
对于 C++,标准的第 12.7 节第 3 段涵盖了这种情况。
To summarize, this is legal. It will resolve to the correct function to the type of the constructor being run. Therefore, adapting your example to C++ syntax, you'd be calling BaseObject::LoadState()
. You can't get to ChildObject::LoadState()
, and trying to do so by specifying the class as well as the function results in undefined behavior.
总而言之,这是合法的。它将解析为正在运行的构造函数类型的正确函数。因此,使您的示例适应 C++ 语法,您将调用BaseObject::LoadState()
. 您无法访问ChildObject::LoadState()
,并尝试通过指定类和函数来实现,这会导致未定义的行为。
Constructors of abstract classes are covered in section 10.4, paragraph 6. In brief, they may call member functions, but calling a pure virtual function in the constructor is undefined behavior. Don't do that.
抽象类的构造函数在第 10.4 节第 6 段中介绍。简而言之,它们可以调用成员函数,但在构造函数中调用纯虚函数是未定义行为。不要那样做。
回答by Mark Brackett
Usually you can get around these issues by having a greedier base constructor. In your example, you're passing an XElement to LoadState. If you allow the state to be directly set in your base constructor, then your child class can parse the XElement prior to calling your constructor.
通常,您可以通过使用更贪婪的基础构造函数来解决这些问题。在您的示例中,您将 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 */) {
}
}
If the child class needs to do a good bit of work, it can offload to a static method.
如果子类需要做一些工作,它可以卸载到静态方法。
回答by Brian
For C++ the base constructor is called before the derived constructor, which means that the virtual table (which holds the addresses of the derived class's overridden virtual functions) does not yet exist. For this reason, it is considered a VERY dangerous thing to do (especially if the functions are pure virtual in the base class...this will cause a pure-virtual exception).
对于 C++,基构造函数在派生构造函数之前被调用,这意味着虚拟表(保存派生类的重写虚函数的地址)尚不存在。出于这个原因,它被认为是一件非常危险的事情(特别是如果函数在基类中是纯虚拟的......这将导致纯虚拟异常)。
There are two ways around this:
有两种方法可以解决这个问题:
- Do a two-step process of construction + initialization
- Move the virtual functions to an internal class that you can more closely control (can make use of the above approach, see example for details)
- 做一个构造+初始化的两步过程
- 将虚函数移到可以更严密控制的内部类中(可以使用上述方法,详见示例)
An example of (1) is:
(1) 的一个例子是:
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
}
};
An example of (2) is:
(2) 的一个例子是:
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 */
};
Approach (2) uses the Factory pattern to provide the appropriate implementation for the accessible
class (which will provide the same interface as your base
class). One of the main benefits here is that you get initialization during construction of accessible
that is able to make use of virtual members of accessible_impl
safely.
方法 (2) 使用工厂模式为类提供适当的实现accessible
(它将提供与您的base
类相同的接口)。这里的主要好处之一是您可以在构建期间进行初始化,accessible
从而能够accessible_impl
安全地使用虚拟成员。
回答by Greg Beech
If you have a class as shown in your post, which takes an XElement
in the constructor, then the only place that XElement
could have come from is the derived class. So why not just load the state in the derived class which already has the XElement
.
如果您有一个类,如您的帖子中所示,它XElement
在构造函数中使用an ,那么唯一XElement
可能来自的地方是派生类。那么为什么不直接在已经具有XElement
.
Either your example is missing some fundamental information which changes the situation, or there's simply no need to chain back up to the derived class with the information from the base class, because it has just told you that exact information.
要么您的示例缺少一些改变情况的基本信息,要么根本不需要使用来自基类的信息链接回派生类,因为它刚刚告诉了您确切的信息。
i.e.
IE
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
}
}
Then your code's really simple, and you don't have any of the virtual method call problems.
那么你的代码真的很简单,而且你没有任何虚方法调用问题。
回答by Johannes Schaub - litb
In C++ it is perfectly safe to call virtual functions from within the base-class - as long as they are non-pure- with some restrictions. However, you shouldn't do it. Better initialize objects using non-virtual functions, which are explicitly marked as being such initialization functions using comments and an appropriate name (like initialize
). If it is even declared as pure-virtual in the class calling it, the behavior is undefined.
在 C++ 中,从基类内部调用虚函数是完全安全的——只要它们是非纯的——有一些限制。但是,您不应该这样做。使用非虚拟函数更好地初始化对象,这些非虚拟函数使用注释和适当的名称(如initialize
)显式标记为此类初始化函数。如果它甚至在调用它的类中被声明为纯虚拟,则行为是未定义的。
The version that's called is the one of the class calling it from within the constructor, and not some overrider in some derived class. This hasn't got much to-do with virtual function tables, but more with the fact that the override of that function might belong to a class that's not yet initialized. So this is forbidden.
被调用的版本是从构造函数中调用它的类之一,而不是某个派生类中的某个覆盖程序。这与虚函数表没有太大关系,但更多的是因为该函数的覆盖可能属于尚未初始化的类。所以这是禁止的。
In C# and Java, that's not a problem, because there is no such thing as a default-initialization that's done just before entering the constructor's body. In C#, the only things that are done outside the body is calling base-class or sibling constructors i believe. In C++, however, initializations done to members of derived classes by the overrider of that function would be undone when constructing those members while processing the constructor initializer list just before entering the constructors body of the derived class.
在 C# 和 Java 中,这不是问题,因为没有在进入构造函数体之前完成的默认初始化这样的事情。在 C# 中,唯一在主体之外完成的事情就是调用基类或兄弟构造函数,我相信。然而,在 C++ 中,当在进入派生类的构造函数主体之前处理构造函数初始值设定项列表时构造这些成员时,由该函数的覆盖程序对派生类成员进行的初始化将被撤消。
Edit: Because of a comment, i think a bit of clarification is needed. Here's an (contrived) example, let's assume it would be allowed to call virtuals, and the call would result in an activation of the final overrider:
编辑:由于评论,我认为需要澄清一下。这是一个(人为的)示例,让我们假设允许调用 virtuals,并且调用将导致最终覆盖程序的激活:
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;
};
That problem can indeed be solved by changing the C++ Standard and allowing it - adjusting the definition of constructors, object lifetime and whatnot. Rules would have to be made to define what str = ...;
means for a not-yet constructed object. And note how the effect of it then depends on who called init
. The feature we get does not justify the problems we have to solve then. So C++ simply forbids dynamic dispatch while the object is being constructed.
这个问题确实可以通过更改 C++ 标准并允许它来解决 - 调整构造函数、对象生命周期等的定义。必须制定规则来定义str = ...;
尚未构造的对象的含义。并注意它的效果如何取决于谁调用了init
. 我们获得的功能并不能证明我们当时必须解决的问题是合理的。因此,C++ 只是在构造对象时禁止动态调度。
回答by ?zgür
For C++, read Scott Meyer's corresponding article :
对于 C++,请阅读 Scott Meyer 的相应文章:
Never Call Virtual Functions during Construction or Destruction
ps: pay attention to this exception in the article:
ps:注意文章中的这个异常:
The problem would almost certainly become apparent before runtime, because the logTransaction function is pure virtual in Transaction. Unless it had been defined (unlikely, butpossible) the program wouldn't link: the linker would be unable to find the necessary implementation of Transaction::logTransaction.
这个问题几乎肯定会在运行之前变得明显,因为 logTransaction 函数在 Transaction 中是纯虚拟的。除非已定义(不太可能,但可能),否则程序不会链接:链接器将无法找到 Transaction::logTransaction 的必要实现。