类型字段是纯粹的邪恶吗?
正如 C++ 编程语言第三版 第 12.2.5 节中所述,输入与使用虚函数和多态性的等效代码相比,字段往往会创建通用性较差、容易出错、直观性较差且可维护性较差的代码。
作为一个简短的示例,以下是类型字段的使用方式:
void print(const Shape &s)
{
switch(s.type)
{
case Shape::TRIANGE:
cout << "Triangle" << endl;
case Shape::SQUARE:
cout << "Square" << endl;
default:
cout << "None" << endl;
}
}
显然,这是一场噩梦,因为向其中添加新的形状类型和十几个类似的函数将很容易出错且繁重。
尽管存在这些缺点以及 TC++PL 中描述的缺点,但是否有任何示例表明这种实现(使用类型字段)是比利用虚拟函数的语言功能更好的解决方案? 或者这种做法应该被列为纯粹邪恶的黑名单吗?
现实的例子比人为的例子更受欢迎,但我仍然对人为的例子感兴趣。另外,您在生产代码中见过这种情况吗(尽管虚拟函数会更容易)?
As discusses in The c++ Programming Language 3rd Edition in section 12.2.5, type fields tend to create code that is less versatile, error-prone, less intuitive, and less maintainable than the equivalent code that uses virtual functions and polymorphism.
As a short example, here is how a type field would be used:
void print(const Shape &s)
{
switch(s.type)
{
case Shape::TRIANGE:
cout << "Triangle" << endl;
case Shape::SQUARE:
cout << "Square" << endl;
default:
cout << "None" << endl;
}
}
Clearly, this is a nightmare as adding a new type of shape to this and a dozen similar functions would be error-prone and taxing.
Despite these shortcomings and those described in TC++PL, are there any examples where such an implementation (using a type field) is a better solution than utilizing the language features of virtual functions? Or should this practice be black listed as pure evil?
Realistic examples would be preferred over contrived ones, but I'd still be interested in contrived examples. Also, have you ever seen this in production code (even though virtual functions would have been easier)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
当您“知道”您有一组非常具体的、小的、恒定的类型时,像这样对它们进行硬编码会更容易。当然,常量不是,变量也不是,所以在某些时候你可能不得不重写整个事情。
这或多或少是 Alexandrescu 的文章< /a>.
例如,如果我正在实现 JSON 库,我知道每个值只能是对象、数组、字符串、整数、布尔值或 Null——规范不允许任何其他值。
When you "know" you have a very specific, small, constant set of types, it can be easier to hardcode them like this. Of course, constants aren't and variables don't, so at some point you might have to rewrite the whole thing anyway.
This is, more or less, the technique used for discriminated unions in several of Alexandrescu's articles.
For example, if I was implementing a JSON library, I'd know each Value can only be an Object, Array, String, Integer, Boolean, or Null—the spec doesn't allow any others.
类型枚举可以通过 memcpy 序列化,而 v-table 则不能。类似的特征是类型枚举值的损坏很容易处理,v表指针的损坏意味着立即损坏。甚至没有可移植的方法来测试 v-table 指针的有效性,调用
dynamic_cast
或typeinfo
对无效对象进行 RTTI 检查是未定义的行为。例如,我选择使用由鉴别器控制的静态分派而不是动态分派的类型层次结构的一个实例是通过 Windows 消息队列传递指向结构的指针。这为我提供了一些保护,防止其他软件可能从我正在使用的范围分配广播消息(它应该保留用于应用程序本地消息,如果您认为实际上遵守了该规则,则不要通过 GO)。
A type enum can be serialized via memcpy, a v-table can't. A similar feature is that corruption of a type enum value is easy to handle, corruption of the v-table pointer means instant badness. There's no portable way to even test a v-table pointer for validity, calling
dynamic_cast
ortypeinfo
to do RTTI checks on an invalid object is undefined behavior.For example, one instance where I choose to use a type hierarchy with static dispatch controlled by a discriminator and not dynamic dispatch is when passing a pointer to a structure through a Windows message queue. This gives me some protection against other software that may have allocated broadcast messages from a range I'm using (it's supposed to be reserved for app-local messages, do not pass GO if you think that rule is actually respected).
以下指南来自 Robert C. Martin 的《Clean Code》。
“我对 switch 语句的一般规则是,如果它们只出现一次,用于创建多态对象,并且隐藏在继承关系后面,以便系统的其余部分看不到它们,那么它们是可以容忍的”。
基本原理是这样的:如果您将类型字段公开给代码的其余部分,您将获得上述 switch 语句的多个实例。这明显违反了 DRY。当您添加类型时,所有这些开关都需要更改(或者更糟糕的是,它们会变得不一致而不会破坏您的构建)。
The following guideline is from Clean Code by Robert C. Martin.
"My general rule for switch statements is that they can be tolerated if they appear only once, are used to create polymorphic objects, and are hidden behind an inheritance relationship so that the rest of the system can't see them".
The rationale is this: if you expose type fields to the rest of your code, you'll get multiple instances of the above switch statement. That is a clear violation of DRY. When you add a type, all these switches need to change (or, even worse, they become inconsistent without breaking your build).
我的看法是:这要看情况。
参数化工厂方法设计模式依赖于这种技术。
那么,这很糟糕吗?我不这么认为,因为现阶段我们没有任何其他选择。这是一个绝对必要的地方,因为它涉及到对象的创建。该物体的类型尚不清楚。
然而,OP 中的示例确实需要重构。这里我们已经在处理现有的对象/类型(作为参数传递给函数)。
作为 Herb萨特提到-
My take is: It depends.
A parameterized Factory Method design pattern relies on this technique.
So, is this bad. I don't think so as we do not have any other alternative at this stage. This is a place where it is absolutely necessary as it involves creation of an object. The type of the object is yet to be known.
The example in OP is however an example which sure needs refactoring. Here we are already dealing with an existing object/type (passed as argument to function).
As Herb Sutter mentions -
难道没有与虚拟函数和多态性相关的成本吗?就像为每个类维护一个 vtable 一样,每个类对象大小增加 4 个字节,运行时缓慢(尽管我从未测量过)以适当地解析虚拟函数。因此,对于简单的情况,使用
type
字段似乎是可以接受的。Aren't there costs associated to virtual functions and polymorphism? Like maintaining a vtable per class, increase of each class object size by 4 byptes, runtime slowness (I have never measured it though) for resolving the virtual function appropriately. So, for simple situations, using a
type
field appears acceptable.我认为如果类型与隐含类精确对应,那么类型就是错误的。事情变得复杂的地方是类型不完全匹配或者没有那么切合和干燥。
举个例子,如果类型是红色、绿色、蓝色会怎样。这些是形状的类型。你甚至可以有一个颜色类作为混合;但可能太多了。
I think that if the type corresponds precisely to the implied classes then type is wrong. Where it gets complicated is where the type does not quite match or its not so cut and dried.
Taking your example what if type was Red, Green, Blue. Those are types of shapes. You could even have a color class as a mixin; but its probably too much.
我正在考虑使用类型字段来解决向量切片的问题。也就是说,我想要一个分层对象的向量。例如,我希望我的向量是形状向量,但我想存储圆形、矩形、三角形等。
由于切片,你无法以最明显的简单方式做到这一点。因此,正常的解决方案是使用指针向量或智能指针。但我认为在某些情况下,使用类型字段将是一个更简单的解决方案(避免新/删除或替代生命周期技术)。
I am thinking of using a type field to solve the problem of vector slicing. That is, I want a vector of hierarchical objects. For example I want my vector to be a vector of shapes, but I want to store circles, rectangles, triangles etc.
You can't do that in the most obvious simple way because of slicing. So the normal solution is to have a vector of pointers or smart pointers instead. But I think there are cases where using a type field will be a simpler solution, (avoids new/delete or alternative lifecycle techniques).
我能想到的最好的例子(也是我之前遇到的例子)是,当你的类型集是固定的,而你想要执行的函数集(取决于这些类型)是可变的。这样,当您添加新函数时,您可以修改单个位置(添加单个开关),而不是添加新的基本虚拟函数,而实际实现则分散在类型层次结构中的所有类中。
The best example I can think of (and the one I've run into before), is when your set of types is fixed and the set of functions you want to do (that depend on those types) is fluid. That way, when you add a new function, you modify a single place (adding a single switch) rather than adding a new base virtual function with the real implementation scattered all across the classes in your type hierarchy.
我不知道任何现实的例子。人为的方法取决于不能使用虚拟方法的一些充分理由。
I don't know of any realistic examples. Contrived ones would depend on there being some good reason that virtual methods cannot be used.