方法链接 + 继承不能很好地协同工作?
考虑一下:
// member data omitted for brevity
// assume that "setAngle" needs to be implemented separately
// in Label and Image, and that Button does need to inherit
// Label, rather than, say, contain one (etc)
struct Widget {
Widget& move(Point newPos) { pos = newPos; return *this; }
};
struct Label : Widget {
Label& setText(string const& newText) { text = newText; return *this; }
Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};
struct Button : Label {
Button& setAngle(double newAngle) {
backgroundImage.setAngle(newAngle);
Label::setAngle(newAngle);
return *this;
}
};
int main() {
Button btn;
// oops: Widget::setText doesn't exist
btn.move(Point(0,0)).setText("Hey");
// oops: calling Label::setAngle rather than Button::setAngle
btn.setText("Boo").setAngle(.5);
}
有什么技术可以解决这些问题吗?
示例:使用模板魔法使 Button::move 返回 Button& 或者其他的东西。
编辑 很明显,第二个问题可以通过将 setAngle 设置为虚拟来解决。
但是第一个问题仍然没有以合理的方式解决!
编辑:嗯,我想在 C++ 中这是不可能正确完成的。 无论如何,感谢您的努力。
Consider:
// member data omitted for brevity
// assume that "setAngle" needs to be implemented separately
// in Label and Image, and that Button does need to inherit
// Label, rather than, say, contain one (etc)
struct Widget {
Widget& move(Point newPos) { pos = newPos; return *this; }
};
struct Label : Widget {
Label& setText(string const& newText) { text = newText; return *this; }
Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};
struct Button : Label {
Button& setAngle(double newAngle) {
backgroundImage.setAngle(newAngle);
Label::setAngle(newAngle);
return *this;
}
};
int main() {
Button btn;
// oops: Widget::setText doesn't exist
btn.move(Point(0,0)).setText("Hey");
// oops: calling Label::setAngle rather than Button::setAngle
btn.setText("Boo").setAngle(.5);
}
Any techniques to get around these problems?
Example: using template magic to make Button::move return Button& or something.
edit It has become clear that the second problem is solved by making setAngle virtual.
But the first problem remains unsolved in a reasonable fashion!
edit: Well, I guess it's impossible to do properly in C++. Thanks for the efforts anyhow.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
对于第二个问题,将 setAngle 设置为虚拟应该可以解决问题。
对于第一个问题,没有简单的解决方案。 Widget::move 返回一个 Widget,它没有 setText 方法。 您可以创建一个纯虚拟 setText 方法,但这将是一个非常丑陋的解决方案。 您可以在按钮类上重载 move() ,但这会很难维护。 最后,您可能可以使用模板做一些事情。 也许是这样的:
我会让你决定哪种解决方案是最干净的。 但是有什么特殊原因需要链接这些方法吗?
For the second problem, making setAngle virtual should do the trick.
For the first one, there are no easy solutions. Widget::move returns a Widget, which doesn't have a setText method. You could make a pure virtual setText method, but that'd be a pretty ugly solution. You could overload move() on the button class, but that'd be a pain to maintain. Finally, you could probably do something with templates. Perhaps something like this:
I'll let you decide which solution is cleanest. But is there any particular reason why you need to be able to chain these methods?
解决问题的一个简单但烦人的方法是在子类中重新实现所有公共方法。 这并不能解决多态性问题(例如,如果您从 Label 转换为 Widget),这可能是也可能不是一个主要问题。
请务必在您的文档中包含此操作,以便链接正常工作。
但实际上,现在为什么要费心使用方法链呢? 很多时候,这些函数的调用会不那么简单,并且需要更长的行。 这确实会损害可读性。 每行一个操作——这也是
++
和--
的一般规则。A simple, but annoying, way of solving your problem is to reimplement all your public methods in your subclasses. This doesn't solve the issue with polymorphism (if you cast from Label to Widget, for example), which may or may not be a major issue.
Be sure to include in your documentation this must be done for chaining to work.
But really, now, why bother with method chaining? Many times, these functions will be called less trivially, and will need longer lines. This would really hurt readability. One action per line -- this is a general rule when it comes to
++
and--
too.Button
真的是Label
吗? 您似乎违反了里氏替换原则。 也许您应该考虑使用装饰器模式来向小部件添加行为。如果您坚持原样结构,您可以像这样解决您的问题:
Is a
Button
really aLabel
? You seem to be violating the Liskov substitution principle. Perhaps you should consider the Decorator pattern to add behaviors to Widgets.If you insist on the structure as is, you can solve your problem like so:
我会放弃链接的东西。 其一,如果不进行一些相对令人讨厌的黑客攻击,实际上就无法做到这一点。 但是,最大的问题是,它使阅读和维护代码变得更加困难,并且很可能最终会导致人们滥用它,创建一大行代码来完成多项任务(回想一下高中代数和那些巨大的代码行)加法、减法和乘法似乎总是在某个时刻结束,如果你允许的话,人们就会这么做)。
另一个问题是,因为系统中的大多数函数都会返回对其自身的引用,所以所有函数都应该返回一个引用,这是合乎逻辑的。 当(不是 if)你最终开始实现也应该返回值的函数(不仅仅是访问器,还有一些修改器,以及其他通用函数)时,你将面临两难的境地,要么打破你的约定(这会滚雪球,使得人们不清楚如何为其他未来的功能实现事物),要么被迫开始通过参数返回值(我相信你会讨厌这一点,就像我认识的大多数其他程序员一样)以及)。
I'd abandon the chaining stuff. For one, it can't actually be done without doing some relatively nasty hacks. But, the biggest problem is that it makes it harder to read and maintain code and you will most likely end up with people abusing it, creating one giant line of code to do multiple things (think back to high-school algebra and those gigantic lines of additions, subtractions and multiplications you always seem to end up with at some point, that is what people will do if you let them).
Another problem is that because most of your functions in the system are going to be returning a reference to itself, its going to be logical that all of them should. When (not if) you finally do start implementing functions that should return values as well (not just accessors, but some mutators will as well, and other generic functions), you will be faced with a dilemma, either break your convention (which will snowball, making it unclear as to how things should be implemented for other future functions) or be forced to start returning values via parameters (which I'm sure you would loathe, as most other programmers I know do as well).
C++ 确实支持虚拟方法的返回值协变。 因此,您可以通过一些工作得到类似您想要的东西:
请注意,您应该使用虚拟方法来执行类似的操作。 如果它们不是虚拟方法,那么“重新实现”的方法将导致名称隐藏,并且调用的方法将取决于变量、指针或引用的静态类型,因此如果基指针或正在使用参考。
C++ does support return value covariance on virtual methods. So you could get something like what you want with a little work:
Note that you should use virtual methods for doing something like this. If they aren't virtual methods, then the 'reimplemented' methods will result in name-hiding and the method called will depend on the static type of the variable, pointer or reference so it might not be he correct method if a base pointer or reference is being used.
[咆哮]
是的。 退出这个方法链业务,直接连续调用函数即可。
说真的,你为允许这种语法付出了代价,而我却没有得到它提供的好处。
[/咆哮]
[rant]
Yes. Quit this method chaining business and just call functions in a row.
Seriously, you pay a price for allowing this syntax, and I don't get the benefits it offers.
[/rant]
不适用于 C++。
C++ 不支持返回类型的差异,因此无法将从 Widget.move() 返回的引用的静态类型更改为比 Widget& 更具体。 即使你覆盖它。
C++ 需要能够在编译时进行检查,因此您不能使用 move 真正返回的是按钮这一事实。
充其量,您可以进行一些运行时转换,但它看起来不会很漂亮。 只是单独通话。
编辑:是的,我很清楚 C++ 标准说返回值协方差是合法的。 然而,在我教授和练习C++时,一些主流编译器(例如VC++)却没有。 因此,为了可移植性,我们建议不要这样做。 最后,当前的编译器可能对此没有任何问题。
Not with C++.
C++ does not support variance in return types, so there's no way to change the static type of the reference returned from Widget.move() to be more specific than Widget& even if you override it.
The C++ needs to be able to check things at compile time, so you can't use the fact that what's really being returned from move is a button.
At best, you can do some runtime casting, but it's not going to look pretty. Just separate calls.
Edit: Yes, I'm well aware of the fact that the C++ standard says that return value covariance is legitimate. However, at the time I was teaching and practicing C++, some mainstream compilers (e.g., VC++) did not. Hence, for portability we recommended against it. It is possible that current compilers have no issue with that, finally.
有一段时间,我认为可能可以重载稍微不寻常的
operator->()
来链接方法而不是.
,但这种想法犹豫不决,因为它似乎是编译器要求->
右侧的标识符属于左侧表达式的 static 类型。 很公平。穷人的方法链接
退一步来说,方法链接的要点是避免重复输入长对象名称。 我建议使用以下快速而肮脏的方法:
而不是“普通形式”:
您可以写:
不,它不像真正的
.
链接那么简洁,但它会节省一些打字时间有很多参数需要设置,而且它确实有一个好处,即它不需要对现有类进行代码更改。 由于您将整组方法调用包装在{}
中以限制引用的范围,因此您始终可以使用相同的短标识符(例如_
或x
)来代表特定的对象名称,可能会增加可读性。 最后,编译器将毫无困难地优化_
。For a while there I thought that it might be possible to overload the slightly unusual
operator->()
for chaining methods instead of.
, but that faltered because it seems the compiler requires the identifier to the right of the->
to belong to the static type of the expression on the left. Fair enough.Poor Man's Method Chaining
Stepping back for a moment, the point of method chaining is to avoid typing out long object names repeatedly. I'll suggest the following quick and dirty approach:
Instead of the "longhand form":
You can write:
No, it's not as succinct as real chaining with
.
, but it will save some typing when there are many parameters to set, and it does have the benefit that it requires no code changes to your existing classes. Because you wrap the entire group of method calls in{}
to limit the scope of the reference, you can always use the same short identifier (e.g._
orx
) to stand for the particular object name, potentially increasing readability. Finally, the compiler will have no trouble optimising away_
.它在 gcc 4.3.2 上编译,有点像 mixin 模式。
This compiles on gcc 4.3.2 and is sort of a mixin pattern.
我认为(我没有测试过)这将使用模板来完成:
注意:
LabelT 和
Label
类)。dynamic_cast
。T&
作为数据成员(派生类将传递this
到基类的构造函数),这将是一个额外的数据成员,但它避免了强制转换,我认为可能允许组合而不是继承或同时允许组合。I think (I haven't tested it) this will do it using templates:
Notes:
LabelT
andLabel
classes).dynamic_cast
if you like.return *this
", the base class could contain aT&
as a data member (the derived class would passthis
to the base class' constructor), which would be an extra data member, but which avoids a cast and I think may permit composition instead of or as well as inheritance.其他人也谈到了设计问题。 您可以使用 C++ 对协变返回类型的支持来解决该问题(尽管以一种相当粗俗的方式)。
事实上,尽管你这样的链接方法会产生奇怪的代码,并且会损害可读性而不是有帮助。 如果你杀死了对自我垃圾的返回引用,你的代码将变成:
Other people have hit on design issues. You can sort of workaround the problem (albeit in a pretty gross fashion) using C++'s support for covariant return types.
Really though chaining methods the way you are makes for strange code, and hurts readability rather than helps. If you killed the return reference to self junk your code would become:
实际上,由于异常安全性较差,方法链接是一个坏主意。 你真的需要它吗?
Really, method chaining is a bad idea due to poor exception safety. Do you really need it?
好吧,您知道它是一个
Button
,因此您应该能够将返回的Widget&
转换为Button&
并继续。 不过它看起来确实有点丑。另一个相当烦人的选项是在
Button
类中为Widget::move
函数(和朋友)创建一个包装器。 如果您有多个函数,那么可能不值得花费精力来包装所有内容。Well, you know it's a
Button
so you should be able to cast the returnedWidget&
as aButton&
and keep going. It does look a bit ugly though.Another rather annoying option is to create a wrapper in your
Button
class for theWidget::move
function (and friends). Probably not worth the effort to wrap everything if you have more than a handful of functions though.setText() 和 setAngle() 真的需要在每个类中返回自己的类型吗? 如果你将它们全部设置为返回 Widget&,那么你可以只使用虚函数,如下所示:
请注意,即使返回类型是 Widget&,按钮或标签级函数仍然会被调用。
Do setText() and setAngle() really need to return their own types in each class? If you set them all to return Widget&, then you can just use virtual functions as follows:
Note that even if the return type is Widget&, the Button- or Label-level functions will still be the ones called.
您可以扩展 CRTP 来处理此问题。 monjardin 的解决方案朝着正确的方向发展。 现在您所需要的只是一个默认的
Label
实现,以将其用作叶类。也就是说,请考虑这样的努力是否值得增加少量的语法奖励。 考虑替代解决方案。
You can extend CRTP to handle this. monjardin's solution goes in the right direction. All you need now is a default
Label
implementation to use it as a leaf class.That said, consider whether such an effort is worth the small added syntax bonus. Consider alternative solutions.