3

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

这个问题的最高投票答案提供了这两个功能:

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;
}

我发现这个功能分开后非常不优雅 - 也更难理解 - 但我觉得我一定错过了一些东西。他为什么不把它们结合起来?

4

2 回答 2

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);
}

当然,在一般情况下,为您创建容器的简单性很好,我们可以轻松地重用更通用的函数来以很少的成本创建第二个函数。

于 2013-04-01T19:01:16.017 回答
3

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

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

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

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));, 来获得相同的行为,但这需要更多的击键并且仍然是一个移动而不是删除整个操作。

于 2013-04-01T19:06:46.953 回答