这两个函数没有组合成一个函数是有原因的吗?

时间:2013-04-01 18:59:26

标签: c++ reference pass-by-reference stdvector

c ++标签下投票最多的问题之一称为"Splitting a string in C++"。在其中,提问者问道:“用C ++分割字符串最优雅的方法是什么?”。

The highest voted answer这个问题提供了这两个功能:

std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}


std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    return split(s, delim, elems);
}

这些功能很棒。但我试图理解为什么回答者没有将这两个函数组合成一个函数。是否有一些性能,可用性或可读性的好处,我没有看到你在结合这些功能时错过了什么?具有组合功能的完整程序如下:

#include <iostream>
#include <vector>
#include <string>
#include <sstream>

using namespace std;    
// splitting them into two seperate functions is unnecessary it seems to me, and makes the underlying function harder to understand.
std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}

int main()
{
    std::vector<std::string> x = split("one:two::three", ':');
    for (int i = 0; i<x.size(); ++i) cout << x[i] << '\n';
    return 0;
}

我发现这个功能分裂得非常不那么优雅 - 而且更难理解 - 但我觉得我必须遗漏一些东西。他为什么不把它们结合起来?

2 个答案:

答案 0 :(得分:4)

想象一下,您将分割一堆不同的来源,但希望结果都以一个容器结束。为此,您不希望该函数始终为您分配新容器:

之前,需要额外的工作:

std::vector<std::string> sources = /* populate */;

std::vector<std::string> results;

for (const auto& source : sources)
{
    auto result = split(source, ":");

    // or maintain a vector of vectors...yuck
    results.insert(std::make_move_iterator(result.begin()),
                   std::make_move_iterator(result.end()));
}

之后,直截了当:

std::vector<std::string> sources = /* populate */;

std::vector<std::string> results;

for (const auto& source : sources)
{
    split(source, ":", results);
}

或者:想象一下你要分割一堆差异来源,为了提高效率,你想要最小化内存分配(你的分析器表示你在这里分配的太多了,例如)。因此,您反复使用相同的向量,以避免在第一次拆分后的后续内存分配。

之前,慢:

std::vector<std::string> sources = /* populate */;

for (const auto& source : sources)
{
    auto result = split(source, ":");

    process(result);
}

之后,更好:

std::vector<std::string> sources = /* populate */;

std::vector<std::string> result;
for (const auto& source : sources)
{
    result.clear();
    split(source, ":", result);

    process(result);
}

当然,在一般情况下,为您创建容器的简单性很好,我们可以轻松地重复使用更通用的功能,以较低的成本创建第二个功能。

答案 1 :(得分:3)

您从原始代码复制的代码提供了两个语义略有不同的函数。第一个添加到现有向量中,即分割结果,而第二个构建在第一个向量之上,创建一个新向量,仅包含来自此 split

通过提供两种功能,您可以提供(具有相同的成本)两种不同的行为,可以满足不同用户的不同需求。如果将它们合并到单个版本中,并且用户需要构建包含从拆分多个字符串获得的所有标记的列表,则必须创建多个向量并合并它们。

有趣的一点,实际上是实际的实现,而不是关于设计一个/两个功能的问题的一部分。第二个功能应该实现为:

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, elems);
    return elems;

}

不同之处在于,您应该直接返回本地变量,而不是返回从内部调用split获得的引用。此更改启用了局部变量的命名返回值优化,从而消除了副本 * 的成本。

* 在C ++ 11中,你也可以使用return std::move(split(s,delim,elems));来获得相同的行为,但这需要更多的击键,而且仍然是 move 而不是删除整个操作。