【问题标题】:C++ Returning Multiple ItemsC++ 返回多个项目
【发布时间】:2012-09-25 15:20:58
【问题描述】:

我正在用 C++ 设计一个从 HTML 页面中提取 URL 的类。我正在使用 Boost 的 Regex 库为我完成繁重的工作。我开始设计一个类,并意识到我不想束缚 URL 的存储方式。一种选择是通过引用接受 std::vector<Url> 并在其上调用 push_back。我想避免强迫我班的消费者使用std::vector。因此,我创建了一个带有目标迭代器的成员模板。它看起来像这样:

template <typename TForwardIterator, typename TOutputIterator>
TOutputIterator UrlExtractor::get_urls(
    TForwardIterator begin,
    TForwardIterator end, 
    TOutputIterator dest);

我觉得我把事情复杂化了。我喜欢用 C++ 编写相当通用的代码,但我很难锁定我的接口。但后来我陷入了这些困境,我试图将一切模板化。此时,阅读代码的人并没有意识到 TForwardIterator 正在迭代 std::string

在我的特殊情况下,我想知道这种通用性是否是一件好事。你什么时候开始让代码更明确?是否有一种标准方法可以从函数中获取值?

【问题讨论】:

  • 首先,您应该考虑使用现有库正确解析 HTML:stackoverflow.com/questions/489522/…
  • 关于不将用户绑定到特定容器,您的设计很好,因为您可以使用back_inserter 作为第三个参数并且与容器无关。这就是许多标准库算法的工作原理。
  • 我真的不需要解析 HTML。我只需要检测锚标签中的hrefs。
  • HTML 过于复杂,无法支持正则表达式解析。任何半复杂的页面都会有脚本标签(或另外十几个标签中的一个),一旦进入那里,一切都会变得很糟糕。您需要使用专门设计用于处理 html 的 SAX 解析器(尤其是所有错误信息的页面(占大多数))。这里描述了一种让你滚动的简单技术:stackoverflow.com/a/11820174/14065

标签: c++ iterator return-value


【解决方案1】:

是的,它不仅很好,而且是一个非常好的设计。以这种方式模板化是大多数标准库算法的工作方式,例如std::fillstd::copy;它们可以与迭代器一起使用,以便您可以填充其中已经包含元素的容器,或者您可以使用一个空容器并使用 std::back_inserter 填充数据。

这是一个非常好的设计 IMO,并利用了模板和迭代器概念的强大功能。

你可以这样使用它(但你已经知道了):

std::list<Url> l;
std::vector<Url> v;

x.get_urls(begin(dat1), end(dat1), std::back_inserter(l));
y.get_urls(begin(dat2), end(dat2), std::back_inserter(v));

我感觉你害怕使用模板,它们不是“正常的”C++,或者它们应该被避免并且过于臃肿等等。我向你保证,它们是非常正常且强大的语言功能,没有其他语言(我所知道的)具有,所以只要适合使用它们,就使用它们。而这里,非常合适。

【讨论】:

    【解决方案2】:

    在我看来你的界面有误。

    已经有从迭代器复制到容器的算法。在我看来,您的课程提供了一个 url 流(不依赖于修改它的源)。因此,您真正需要的只是一种通过迭代器(前向迭代器)公开内部数据的方法,因此您只需要提供 begin() 和 end()。

    UrlExtractor             page(/* Some way of constructing page */);
    std::vector<std::string> data;
    
    std::copy(page.begin(), page.end(), std::back_inserter(data));
    

    我只提供以下接口:

    class UrlExtractor
    {
        ...... STUFF
        iterator begin(); 
        iterator end();
    };
    

    【讨论】:

    • 题外话,但我不知道你的头像是强烈的左眼还是悲伤的右眼:)
    • 我有想过这个界面。我关心的是明确指定迭代器的类型。例如,我不确定我是否会处理 unicode。我想我可以让整个类成为输入字符类型的模板。
    【解决方案3】:

    是的,你太笼统了。模板的意义在于您可以生成行为不同的函数的多个副本。您可能不希望这样,因为您应该选择一种表示 URL 的方式并在整个程序中使用它。

    你就这样吧:

    typedef std::string url;
    

    这使您可以更改将来用于 url 的类。

    也许std::vector 实现了一些带有push_back() 的接口,您的方法可以引用该接口 (back_inserter?)。

    【讨论】:

    • 我想他想弄清楚如何存储一个 collection 的 url,而不是一个。
    • 对。我想知道如何从函数中获取URLs。我已经有一个 URL 类。我想知道迭代器模板是否过于矫枉过正,是否应该明确集合类型,例如std::vector
    • @TravisParks 不,你现在做得很好:) 看看std::fillstd::copy,基本上其他所有需要迭代器的东西;它们旨在与神奇的back_inserter 一起用于已经有空间的容器的普通迭代器。
    • 你不觉得会员模板太多了吗?我觉得 STL 和 Boost 的通用性正在渗入我的代码库的其余部分。也许这对 C++ 来说是正常的——我不知道。
    • @TravisParks 不,我觉得很漂亮,看我的回答。
    【解决方案4】:

    不知道实际用例场景很难说,但在 一般来说,最好避免使用模板(或任何其他不必要的 复杂性)除非它真的给你买了东西。最明显的 这里的签名是:

    std::vector<Url> UrlExtractor::get_urls( std::string const& source );
    

    真的有任何可能的情况,你必须解析任何东西 但是std::string? (如果您还支持输入,可能会有 迭代器。但在实践中,如果你正在解析,来源将是 std::stringstd::istream&amp;。除非你真的想 支持后者,只需使用std::string。)当然还有客户端代码 可以对返回的向量做任何事情,包括附加 它到另一种类型的集合。

    如果返回std::vector 的成本确实成为问题,那么您 可以将std::vector&lt;Url&gt;&amp; 作为参数。我什么都看不到 任何额外的灵活性都会给你带来很多好处的合理场景, 像get_urls 这样的函数可能相当复杂,并且 不是你想放在标题中的那种东西。

    【讨论】:

    • 在 C++11 中,按值返回 vector 的成本几乎为零。
    • @SethCarnegie 即使在 C++03 中,也有很多时候 RVO 或 NRVO 会消除所有成本。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-11
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    • 2022-10-23
    • 1970-01-01
    • 2014-04-23
    相关资源
    最近更新 更多