为什么std::sub_match<T>公开继承自std::pair<T, T>?

10
我正在阅读 std::sub_match<BidirectionalIterator> 的文档,发现它公开继承自 std::pair<BidirectionalIterator, BidirectionalIterator>。由于 sub_match 只是一对字符序列中的迭代器,并带有一些附加功能,我可以理解为什么要用 pair 实现它,但为什么要使用公共继承呢?
std::pair<T,U> 公共继承会出现与从大多数其他标准类公共继承相同的问题:它们不应该被多态地操作(尤其是它们没有定义虚析构函数)。其他成员也将无法正常工作,特别是赋值运算符和交换成员函数(它们不会复制 sub_matchmatched 成员)。
为什么 Boost 开发人员以及委员会决定通过公共继承来实现 sub_match,而不是使用组合(或私有继承和使用声明,如果他们想保留通过 firstsecond 进行成员访问)?
5个回答

5

这是一个有趣的问题。可能是因为没有人会动态分配sub_match对象,所以他们认为它是安全的。你唯一能获得sub_match对象的方式是作为basic_regex的某些函数的返回值,或者是其他sub_match的拷贝,并且所有这些对象都将是临时对象或局部变量。

请注意,即使是保留sub_match对象也不安全,因为它们包含了迭代器,但标准中并没有指定它们的生命周期...直到match_results对象被重复使用?直到用于填充match_results对象的string操作数被销毁?还是其他情况?

我仍然会避免公共继承。但在这种情况下,它并不像看起来那么危险,因为你实际上没有理由动态分配sub_match


我同意动态分配应该尽可能避免。然而,=swap仍可能出现问题。例如,我考虑过使用Boost.Range,但它不要求范围可分配或可交换。值得注意的是,Boost.Range算法不接受sub_match作为参数,但如果通过对pair的引用进行操作,则可以这样做(特性类问题)。 - Luc Touraille

3
因为C++没有不使用公共继承来继承接口的方法。您可以使用私有继承继承实现,但然后所有内容都是私有的。如果您想要与std::pair相同的接口,则必须成为std::pair
另外,请考虑这一点。这显然是未定义的行为:
std::sub_match<BidirectionalIterator> theMatch = ...;
std::pair<BidirectionalIterator> *pMatch = &theMatch;
delete pMatch;

但这样做也可以:
std::sub_match<BidirectionalIterator> theMatch = ...;
std::pair<BidirectionalIterator> *pMatch = &theMatch.pair;
delete pMatch;

为什么第一个比第二个更值得关注?

sub_matchpair都是轻量级对象(当然要取决于它们的内容)。它们旨在被复制或按引用传递,所有这些都是百分之百安全的。几乎没有理由将它们分配到堆上并通过指针使用。因此,虽然我理解你的担忧,但我认为在任何实际代码中都不太可能发生。


实际上,你的第二个例子和第一个例子一样有缺陷,但这是因为你认为pair成员将是公共的:为什么要假设会使用这样糟糕的封装呢?虽然我同意删除可能永远不会成为问题,但我不同意通过引用传递是100%安全的,因为还有其他非虚拟成员函数。 - Luc Touraille
你可以使用私有继承来继承(部分)公共接口:class sub_match : pair { using pair.first; using pair.second; bool matched; } - JohannesD
如果你想要与std::pair相同的接口,但不想成为一个std::pair,那么你可以使用私有继承以及使用声明和/或委托。当然,这对于子类的开发者来说需要更多的工作量。 - Luc Touraille

3

0

因为他们不需要虚析构函数?;-)


析构函数不是唯一可能无法正常工作的成员:赋值运算符和交换函数都只会复制 pair 成员,而不会复制 sub_match 中包含的成员(特别是 matched 布尔值)。 - Luc Touraille
@LucTouraille 很好的观点。很明显,'sub_match'并不是一个'pair'。 - James Kanze
@JamesKanze:你说得对,“is-a”关系不适用于这两个类,里氏替换原则没有被遵守。 - Luc Touraille
@MichaelKrelin-hacker:这就是重点:它不仅仅是一对。它显然有一个matched布尔成员。而且它并不适用于所有接受pair的地方(参见Boost.Range)。 - Luc Touraille
是的,你说得对,这不止是一对。不确定为什么“pair”在被接受的地方无法起作用,但没关系。 - Michael Krelin - hacker
显示剩余5条评论

0
如果std::sub_match<BidirectionalIterator>没有自己的状态,那么它从std::pair继承就可以了。但是不要在家里这样做。

状态与此无关。如果您通过pair<>指针删除sub_match对象,则行为未定义。 - James Kanze
2
此外,它确实有自己的状态(一个布尔值,指示匹配是否成功)。 - Luc Touraille

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接