测试 c++特征类
我有一组类,它们描述了一组可以容纳事物并对它们执行操作的逻辑框。我
struct IBox // all boxes do these
{
....
}
struct IBoxCanDoX // the power to do X
{
void x();
}
struct IBoxCanDoY // the power to do Y
{
void y();
}
想知道这些类的客户端处理这些可选功能的“最佳”或可能只是“最喜欢”的惯用语
a)
if(typeid(box) == typeid(IBoxCanDoX))
{
IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box);
ix->x();
}
b)
IBoxCanDoX *ix = dynamic_cast<IBoxCanDoX*>(box);
if(ix)
{
ix->x();
}
c)
if(box->canDoX())
{
IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box);
ix->x();
}
d)现在不同的类结构
struct IBox
{
void x();
void y();
}
...
box->x(); /// ignored by implementations that dont do x
e)相同除了
box->x() // 'not implemented' exception thrown
f)显式测试 功能
if(box->canDoX())
{
box->x();
}
我相信还有其他
。编辑:
为了使用例更清晰,
我通过交互式用户界面向最终用户公开这些内容。他们可以输入“make box do X”。我需要知道 box 是否可以做 x 。或者我需要禁用“使当前框执行X”命令
编辑2:感谢所有回答者,
正如诺亚·罗伯茨指出的(a)不起作用(解释了我的一些问题!)。 我最终做了 (b) 和一个轻微的变体
template<class T>
T* GetCurrentBox()
{
if (!current_box)
throw "current box not set";
T* ret = dynamic_cast<T*>(current_box);
if(!ret)
throw "current box doesnt support requested operation";
return ret;
}
...
IBoxCanDoX *ix = GetCurrentBox<IBoxCanDoX>();
ix->x();
,让 UI 管道很好地处理异常(我并不是真的抛出裸字符串)。
我也打算探索访客
I have a set of classes that describe a set of logical boxes that can hold things and do things to them. I have
struct IBox // all boxes do these
{
....
}
struct IBoxCanDoX // the power to do X
{
void x();
}
struct IBoxCanDoY // the power to do Y
{
void y();
}
I wonder what is the 'best' or maybe its just 'favorite' idiom for a client of these classes to deal with these optional capabilities
a)
if(typeid(box) == typeid(IBoxCanDoX))
{
IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box);
ix->x();
}
b)
IBoxCanDoX *ix = dynamic_cast<IBoxCanDoX*>(box);
if(ix)
{
ix->x();
}
c)
if(box->canDoX())
{
IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box);
ix->x();
}
d) different class struct now
struct IBox
{
void x();
void y();
}
...
box->x(); /// ignored by implementations that dont do x
e) same except
box->x() // 'not implemented' exception thrown
f) explicit test function
if(box->canDoX())
{
box->x();
}
I am sure there are others too.
EDIT:
Just to make the use case clearer
I am exposing this stuff to end users via interactive ui. They can type 'make box do X'. I need to know if box can do x. Or I need to disable the 'make current box do X' command
EDIT2: Thx to all answerers
as Noah Roberts pointed out (a) doesnt work (explains some of my issues !).
I ended up doing (b) and a slight variant
template<class T>
T* GetCurrentBox()
{
if (!current_box)
throw "current box not set";
T* ret = dynamic_cast<T*>(current_box);
if(!ret)
throw "current box doesnt support requested operation";
return ret;
}
...
IBoxCanDoX *ix = GetCurrentBox<IBoxCanDoX>();
ix->x();
and let the UI plumbing deal nicely with the exceptions (I am not really throwing naked strings).
I also intend to explore Visitor
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
我建议使用访客模式来解决 C++ 中的双重调度问题:
I suggest the Visitor pattern for double-dispatch problems like this in C++:
在您给出的选项中,我认为 b 或 d 是“最佳”。然而,需要做大量此类事情通常表明设计很差,或者用动态类型语言而不是 C++ 可以更好地实现设计。
Of the options you've given, I'd say that b or d are "best". However, the need to do a lot of this sort of thing is often indicative of a poor design, or of a design that would be better implemented in a dynamically typed language rather than in C++.
如果您使用“I”前缀来表示“接口”,就像 Java 中的含义一样,而这将通过 C++ 中的抽象基来完成,那么您的第一个选项将无法工作......所以这个选项就被淘汰了。不过我已经用它来做一些事情了。
不要做“d”,它会污染你的层次结构。保持你的界面干净,你会很高兴你这样做的。因此,Vehicle 类没有pedal() 函数,因为只有某些车辆可以踩踏。如果客户端需要pedal()函数,那么它确实需要了解那些可以实现的类。
远离“e”,原因与“d”相同,而且它违反了里氏替换原则。如果客户端需要在调用某个类之前检查该类是否响应pedal(),以便它不会爆炸,那么最好的方法是尝试转换为具有该功能的对象。 “f”与支票是一样的。
“c”是多余的。如果您按照应有的方式设置了层次结构,那么转换为 ICanDoX 就足以检查 x 是否可以执行 X()。
因此,“b”成为您从给定选项中得到的答案。然而,正如 Gladfelter 所展示的那样,您在帖子中还没有考虑到一些选项。
编辑说明:我没有注意到“c”使用的是 static_cast 而不是动态的。正如我在回答中提到的,dynamic_cast 版本更干净,应该是首选,除非特定情况另有规定。它与以下选项类似,它会污染基本接口。
编辑2:我应该注意,关于“a”,我已经使用了它,但我不像您在帖子中那样静态使用类型。每当我使用 typeid 根据类型分割流时,它总是基于运行时注册的内容。例如,打开正确的对话框来编辑某些未知类型的对象:对话框调控器根据它们编辑的类型向工厂注册。这使我在添加/删除/更改对象时不必更改任何流程控制代码。在不同情况下我通常不会使用此选项。
If you are using the 'I' prefix to mean "interface" as it would mean in Java, which would be done with abstract bases in C++, then your first option will fail to work....so that one's out. I have used it for some things though.
Don't do 'd', it will pollute your hierarchy. Keep your interfaces clean, you'll be glad you did. Thus a Vehicle class doesn't have a pedal() function because only some vehicles can pedal. If a client needs the pedal() function then it really does need to know about those classes that can.
Stay way clear of 'e' for the same reason as 'd' PLUS that it violates the Liskov Substitution Principle. If a client needs to check that a class responds to pedal() before calling it so that it doesn't explode then the best way to do that is to attempt casting to an object that has that function. 'f' is just the same thing with the check.
'c' is superfluous. If you have your hierarchy set up the way it should be then casting to ICanDoX is sufficient to check if x can do X().
Thus 'b' becomes your answer from the options given. However, as Gladfelter demonstrates, there are options you haven't considered in your post.
Edit note: I did not notice that 'c' used a static_cast rather than dynamic. As I mention in an answer about that, the dynamic_cast version is cleaner and should be preferred unless specific situations dictate otherwise. It's similar to the following options in that it pollutes the base interface.
Edit 2: I should note that in regard to 'a', I have used it but I don't use types statically like you have in your post. Any time I've used typeid to split flow based on type it has always been based on something that is registered during runtime. For example, opening the correct dialog to edit some object of unknown type: the dialog governors are registered with a factory based on the type they edit. This keeps me from having to change any of the flow control code when I add/remove/change objects. I generally wouldn't use this option under different circumstances.
A 和 B 需要运行时类型识别 (RTTI),如果进行大量检查,速度可能会变慢。就我个人而言,我不喜欢“canDoX”方法的解决方案,如果出现这种情况,设计可能需要升级,因为您暴露了与类不相关的信息。
如果您只需要执行 X 或 Y,具体取决于类,我会选择 IBox 中的虚拟方法,该方法会在子类中被重写。
如果该解决方案不适用或者您需要更复杂的逻辑,请查看访问者设计模式。但请记住,当您定期添加新类或方法更改/添加/删除时,访问者模式不是很灵活(但这也适用于您建议的替代方案)。
A and B require run time type identification(RTTI) and might be slower if you are doing a lot checks. Personally I don't like the solutions of "canDoX" methods, if situations like this arise the design probably needs an upgrade because you are exposing information that is not relevant to the class.
If you only need to execute X or Y, depending on the class, I would go for a virtual method in IBox which get overridden in subclasses.
If that solution is not applicable or you need more complex logic, then look at the Visitor design pattern. But keep in mind that the visitor pattern is not very flexible when you add new classes regularly or methods change/are added/are removed (but that also goes true for your proposed alternatives).
如果您尝试从代码的偶然部分调用这些类中的任何一个操作,我建议您将该代码包装在模板函数中,并以实现鸭子类型相同的方式命名每个类的方法,因此您的客户端代码将如下所示。
If you are trying to call either of these classes actions from contingent parts of code, you I would suggest you wrap that code in a template function and name each class's methods the same way to implement duck typing, thus your client code would look like this.
您的问题没有通用答案。一切都取决于。我只能说:
- 不要使用a),而是使用b)
- b) 很好,需要最少的代码,不需要虚拟方法,但dynamic_cast有点慢
- c) 与 b) 类似,但速度更快(无dynamic_cast)并且需要更多内存
- e) 没有意义,你仍然需要发现是否可以调用该方法,这样就不会抛出异常
- d) 比 f) 更好(需要编写的代码更少)
- d) e) 和 f) 比其他代码产生更多垃圾代码,但速度更快且内存消耗更少
There is no general answer to your question. Everything depends. I can say only that:
- don't use a), use b) instead
- b) is nice, requires least code, no need for dummy methods, but dynamic_cast is a little slow
- c) is similar to b) but it is faster (no dynamic_cast) and requires more memory
- e) has no sense, you still need to discover if you can call the method so the exception is not thrown
- d) is better then f) (less code to write)
- d) e) and f) produce more garbage code then others, but are faster and less memory consuming
我假设您在这里不仅会处理一种类型的一个对象。
我会布置您正在使用的数据,并尝试了解如何将其布置在内存中,以便进行数据驱动编程。良好的内存布局应该反映您在类中存储数据的方式以及类在内存中的布局方式。一旦你有了基本的设计结构(不应该超过一张餐巾纸),我将开始根据你计划对数据执行的操作将对象组织到列表中。如果您计划对子集 X 中的对象集合 { Y } 执行 X() 操作,我可能会确保从一开始就创建一个 Y 的静态数组。如果您希望偶尔访问整个 X,可以通过将列表收集到动态指针列表中来安排(使用 std::vector 或您最喜欢的选择)。
我希望这是有道理的,但一旦实施,它就会提供简单直接的解决方案,易于理解和使用。
I assume that you will not only be working with one object of one type here.
I would lay out the data that you are working with and try to see how you can lay it out in memory in order to do data-driven programming. A good layout in memory should reflect the way that you store the data in your classes and how the classes are layed out in memory. Once you have that basic design structured (shouldn't take more than a napkin), I would begin organizing the objects into lists dependent on the operations that you plan to do on the data. If you plan to do X() on a collection of objects { Y } in the subset X, I would probably make sure to have a static array of Y that I create from the beginning. If you wish to access the entire of X occasionally, that can be arranged by collecting the lists into a dynamic list of pointers (using std::vector or your favorite choice).
I hope that makes sense, but once implemented it gives simple straight solutions that are easy to understand and easy to work with.
有一种通用方法可以测试一个类是否支持某个概念,然后执行最合适的代码。它使用 SFINAE hack。此示例的灵感来自 Abrahams 和 Gurtovoy 的“C++ 模板元编程”一书。如果存在,函数doIt将使用x方法,否则将使用y方法。您也可以扩展 CanDo 结构来测试其他方法。您可以根据需要测试任意多个方法,只要可以唯一解决 doIt 的重载即可。
There is a generic way to test if a class supports a certain concept and then to execute the most appropriate code. It uses SFINAE hack. This example is inspired by Abrahams and Gurtovoy's "C++ Template Metaprogramming" book. The function doIt will use x method if it is present, otherwise it will use y method. You can extend CanDo structure to test for other methods as well. You can test as many methods as you wish, as long as the overloads of doIt can be resolved uniquely.