C++ 中的惰性/多阶段构造
对于 C++ 中对象的多阶段构造/初始化,现有的良好类/设计模式是什么?
我有一个类,其中包含一些数据成员,这些数据成员应该在程序流程中的不同点进行初始化,因此必须延迟它们的初始化。例如,一个参数可以从文件中读取,另一个参数可以从网络中读取。
目前,我正在使用 boost::Optional 来延迟构造数据成员,但令我困扰的是,可选在语义上与延迟构造不同。
我需要的是 boost::bind 和 lambda 部分函数应用程序的功能,并且使用这些库我可能可以设计多阶段构造 - 但我更喜欢使用现有的、经过测试的类。 (或者也许还有另一种我不熟悉的多阶段构建模式)。
What's a good existing class/design pattern for multi-stage construction/initialization of an object in C++?
I have a class with some data members which should be initialized in different points in the program's flow, so their initialization has to be delayed. For example one argument can be read from a file and another from the network.
Currently I am using boost::optional for the delayed construction of the data members, but it's bothering me that optional is semantically different than delay-constructed.
What I need reminds features of boost::bind and lambda partial function application, and using these libraries I can probably design multi-stage construction - but I prefer using existing, tested classes. (Or maybe there's another multi-stage construction pattern which I am not familiar with).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
关键问题是是否应该在类型级别区分完全填充的对象和不完全填充的对象。如果您决定不做区分,那么只需使用
boost::Optional
或类似的方法即可:这样可以轻松快速地进行编码。 OTOH,您无法让编译器强制执行特定函数需要完全填充的对象的要求;您每次都需要对字段执行运行时检查。参数组类型
如果确实在类型级别区分完全填充的对象和不完全填充的对象,则可以强制要求向函数传递完整的对象。为此,我建议为每个相关类型
X
创建相应的类型XParams
。XParams
具有boost::optional
成员和每个参数的 setter 函数,可以在初始构造后设置。然后,您可以强制X
仅具有一个(非复制)构造函数,该构造函数将XParams
作为其唯一参数,并检查每个必要的参数是否已在该X 中设置。代码>XParams
对象。 (不确定这个模式是否有名称——有人喜欢编辑它来填充我们吗?)“部分对象”类型
如果您实际上不需要对对象做任何事情,那么这会非常有用。在它完全填充之前(也许除了像取回字段值这样的琐碎事情之外)。如果有时确实必须将未完全填充的
X
视为“完整”X
,则可以使X
从类型派生>XPartial
,其中包含所有逻辑,以及用于执行前提条件测试的受保护的虚拟方法,以测试是否填充了所有必需的字段。然后,如果 X 确保它只能在完全填充的状态下构造,它可以通过始终返回 true 的简单检查来覆盖那些受保护的方法:尽管看起来可能这里的继承是“从后到前”,这样做意味着可以在任何需要
XPartial&
的地方安全地提供X
,因此这种方法遵循 < a href="http://en.wikipedia.org/wiki/Liskov_substitution_principle" rel="nofollow noreferrer">里氏替换原理。这意味着函数可以使用X&
参数类型来指示它需要一个完整的X
对象,或使用XPartial&
来指示它可以处理部分填充的对象——在这种情况下,可以传递XPartial
对象或完整的X
对象。最初我将
isComplete()
作为protected
,但发现这不起作用,因为X
的复制构造函数和赋值运算符必须调用此函数他们的XPartial&
参数,并且他们没有足够的访问权限。经过反思,公开公开此功能更有意义。The key issue is whether or not you should distinguish completely populated objects from incompletely populated objects at the type level. If you decide not to make a distinction, then just use
boost::optional
or similar as you are doing: this makes it easy to get coding quickly. OTOH you can't get the compiler to enforce the requirement that a particular function requires a completely populated object; you need to perform run-time checking of fields each time.Parameter-group Types
If you do distinguish completely populated objects from incompletely populated objects at the type level, you can enforce the requirement that a function be passed a complete object. To do this I would suggest creating a corresponding type
XParams
for each relevant typeX
.XParams
hasboost::optional
members and setter functions for each parameter that can be set after initial construction. Then you can forceX
to have only one (non-copy) constructor, that takes anXParams
as its sole argument and checks that each necessary parameter has been set inside thatXParams
object. (Not sure if this pattern has a name -- anybody like to edit this to fill us in?)"Partial Object" Types
This works wonderfully if you don't really have to do anything with the object before it is completely populated (perhaps other than trivial stuff like get the field values back). If you do have to sometimes treat an incompletely populated
X
like a "full"X
, you can instead makeX
derive from a typeXPartial
, which contains all the logic, plusprotected
virtual methods for performing precondition tests that test whether all necessary fields are populated. Then ifX
ensures that it can only ever be constructed in a completely-populated state, it can override those protected methods with trivial checks that always returntrue
:Although it might seem the inheritance here is "back to front", doing it this way means that an
X
can safely be supplied anywhere anXPartial&
is asked for, so this approach obeys the Liskov Substitution Principle. This means that a function can use a parameter type ofX&
to indicate it needs a completeX
object, orXPartial&
to indicate it can handle partially populated objects -- in which case either anXPartial
object or a fullX
can be passed.Originally I had
isComplete()
asprotected
, but found this didn't work sinceX
's copy ctor and assignment operator must call this function on theirXPartial&
argument, and they don't have sufficient access. On reflection, it makes more sense to publically expose this functionality.我一定错过了一些东西——我一直在做这种事情。在所有情况下,类都不需要很大的对象和/或不需要的对象是很常见的。所以动态地创建它们!
我不认为需要任何比这更奇特的东西,除了 makebig() 可能应该是 const (并且可能是内联),并且 Big 指针可能应该是可变的。当然,A 必须能够构造 Big,这在其他情况下可能意味着缓存所包含的类的构造函数参数。您还需要决定复制/作业策略 - 对于此类课程,我可能会禁止这两种策略。
I must be missing something here - I do this kind of thing all the time. It's very common to have objects that are big and/or not needed by a class in all circumstances. So create them dynamically!
I don't see the need for anything fancier than that, except that makebig() should probably be const (and maybe inline), and the Big pointer should probably be mutable. And of course A must be able to construct Big, which may in other cases mean caching the contained class's constructor parameters. You will also need to decide on a copying/assignment policy - I'd probably forbid both for this kind of class.
我不知道有什么模式可以处理这个特定问题。这是一个棘手的设计问题,对于 C++ 等语言来说有些独特。另一个问题是,这个问题的答案与您的个人(或公司)编码风格密切相关。
我会为这些成员使用指针,当需要构造它们时,同时分配它们。您可以对它们使用 auto_ptr,并检查 NULL 以查看它们是否已初始化。 (我认为指针是 C/C++/Java 中内置的“可选”类型,还有其他语言,其中 NULL 不是有效的指针)。
风格方面的一个问题是您可能依赖构造函数来完成太多工作。当我编写面向对象的代码时,我让构造函数完成足够的工作以使对象处于一致状态。例如,如果我有一个
Image
类,并且我想从文件中读取数据,我可以这样做:或者,我可以这样做: 如果
如果构造函数中有很多代码,特别是如果您问“如果构造函数失败会发生什么?”的问题。这是将代码移出构造函数的主要好处。
我还有更多话要说,但我不知道你想对延迟施工做什么。
编辑:我记得有一种(有点反常的)方法可以在任意时间调用对象的构造函数。这里有一个例子:
请注意,做这样鲁莽的事情可能会招致其他程序员的蔑视,除非你有充分的理由使用这种技术。这也不会延迟构造函数,只是让您稍后再次调用它。
I don't know of any patterns to deal with this specific issue. It's a tricky design question, and one somewhat unique to languages like C++. Another issue is that the answer to this question is closely tied to your individual (or corporate) coding style.
I would use pointers for these members, and when they need to be constructed, allocate them at the same time. You can use auto_ptr for these, and check against NULL to see if they are initialized. (I think of pointers are a built-in "optional" type in C/C++/Java, there are other languages where NULL is not a valid pointer).
One issue as a matter of style is that you may be relying on your constructors to do too much work. When I'm coding OO, I have the constructors do just enough work to get the object in a consistent state. For example, if I have an
Image
class and I want to read from a file, I could do this:or, I could do this:
It can get difficult to reason about how a C++ program works if the constructors have a lot of code in them, especially if you ask the question, "what happens if a constructor fails?" This is the main benefit of moving code out of the constructors.
I would have more to say, but I don't know what you're trying to do with delayed construction.
Edit: I remembered that there is a (somewhat perverse) way to call a constructor on an object at any arbitrary time. Here is an example:
Note that doing something as reckless as this might earn you the scorn of your fellow programmers, unless you've got solid reasons for using this technique. This also doesn't delay the constructor, just lets you call it again later.
对于某些用例来说,使用 boost.Optional 看起来是一个很好的解决方案。我没玩过太多,所以不能发表太多评论。在处理此类功能时,我要记住的一件事是我是否可以使用重载构造函数而不是默认构造函数和复制构造函数。
当我需要这样的功能时,我只需使用指向必要字段类型的指针,如下所示:
接下来,我将通过以下方法访问该字段,而不是使用指针:
我省略了 const-access 语义
Using boost.optional looks like a good solution for some use cases. I haven't played much with it so I can't comment much. One thing I keep in mind when dealing with such functionality is whether I can use overloaded constructors instead of default and copy constructors.
When I need such functionality I would just use a pointer to the type of the necessary field like this:
next, instead of using the pointer I would access the field through the following method:
I have omitted const-access semantics
我所知道的最简单的方法类似于 Dietrich Epp 建议的技术,只不过它允许您真正延迟对象的构造,直到您选择的时刻。
基本上:使用 malloc 而不是 new 保留对象(从而绕过构造函数),然后当您真正想要通过放置 new 构造对象时调用重载的 new 运算符。
示例:
就我个人而言,我唯一一次使用此语法是当我必须为 C++ 对象使用基于 C 的自定义分配器时。正如 Dietrich 所建议的,您应该质疑是否真的必须延迟构造函数调用。 base 构造函数应该执行最低限度的操作以使对象进入可用状态,而其他重载构造函数可能会根据需要执行更多工作。
The easiest way I know is similar to the technique suggested by Dietrich Epp, except it allows you to truly delay the construction of an object until a moment of your choosing.
Basically: reserve the object using malloc instead of new (thereby bypassing the constructor), then call the overloaded new operator when you truly want to construct the object via placement new.
Example:
Personally, the only time I've ever used this syntax was when I had to use a custom C-based allocator for C++ objects. As Dietrich suggests, you should question whether you really, truly must delay the constructor call. The base constructor should perform the bare minimum to get your object into a serviceable state, whilst other overloaded constructors may perform more work as needed.
我不知道这是否有正式的模式。在我见过的地方,我们称之为“惰性”、“需求”或“按需”。
I don't know if there's a formal pattern for this. In places where I've seen it, we called it "lazy", "demand" or "on demand".