在制作std :: string副本时的分配

时间:2011-03-09 08:06:52

标签: c++ string stl

您认为哪种实施更好?

std::string ToUpper( const std::string& source )
{
    std::string result;
    result.resize( source.length() );
    std::transform( source.begin(), source.end(), result.begin(), 
        std::ptr_fun<int, int>( std::toupper ) );
    return result;
}

和...

std::string ToUpper( const std::string& source )
{
    std::string result( source.length(), '\0' );
    std::transform( source.begin(), source.end(), result.begin(), 
        std::ptr_fun<int, int>( std::toupper ) );
    return result;
}

区别在于第一个在默认构造函数之后使用reserve方法,但第二个使用构造函数接受字符数。

修改 1.我不能使用boost lib。 2.我只想比较在构造函数中的分配和构造函数之后的分配。

8 个答案:

答案 0 :(得分:5)

一个简单的事情:

std::string result;
result.reserve( source.length() );

std::transform( source.begin(), source.end(), 
                std::back_inserter( result ),
                ::toupper );

然后我个人会改用boost。

答案 1 :(得分:3)

这两个实现中的第一个是不正确的。 string::reserve函数不会改变字符串的大小;相反,它只是为它分配内部缓冲区空间。这意味着如果您开始写入字符串,则会覆盖物理字符串的一部分但不是逻辑部分字符串的字符。也就是说,字符在那里,但字符串的长度不会将它们报告为字符串的一部分。

但是,我明白为什么你试图以这种方式使用reserve:如果使用resize使字符串变大,那么所有字符都必须分配一些值对他们而言,这可能会给该计划带来一些低效率。因此,您正在寻找一些方法来避免这种成本。如果要执行此操作,请考虑使用以下代码:

string ToUpper(const std::string& source) {
    std::string result;
    result.reserve(source.length());

    for (string::const_iterator itr = source.begin(); itr != source.end(); ++itr)
        result += std::toupper(*itr);

    return result;
}

首先在结果字符串上调用reserve以预先为其分配存储空间。然后,我们只需遍历第一个字符串的元素,将每个元素转换为大写字母,然后使用operator +=将其附加到结果字符串。由于调用reserve,因此字符串中已经存在空格,因此不应该涉及任何新的分配,并且只使用您想要的字符写入缓冲区。

希望这有帮助!

答案 2 :(得分:3)

#include <boost/algorithm/string.hpp>

std::string copy = boost::to_upper_copy(original);

答案 3 :(得分:2)

第二个更好,因为它不太可能通过不可重复的迭代器进行写入。如果您只保留了存储空间,则需要附加字符。该字符串仍为空。考虑std::back_insert_iterator

答案 4 :(得分:1)

直接循环是最简单的:

std::string toUpper(std::string s)
{
    for(size_t i = 0, j = s.size(); i != j; ++i)
        s[i] = toupper(s[i]);
    return s;
}

这里不需要使用标准算法和仿函数。

<强> [更新]

你提到你不能使用boost。我对以上版本进行了基准测试,该版本将结果字符串写入两次,而后者仅写入一次:

struct ToUpper
{
    typedef char result_type;
    char operator()(char c) const { return toupper(c); }
};

std::string toUpper2(std::string const& s)
{
    boost::transform_iterator<ToUpper, std::string::const_iterator> beg(s.begin());
    return std::string(beg, beg + s.size());
}

结果是:

500000 calls
toUpper : 414nsec/call
toUpper : 414nsec/call
toUpper : 414nsec/call
toUpper2: 301nsec/call
toUpper2: 302nsec/call
toUpper2: 302nsec/call

答案 5 :(得分:1)

既然你要返回一个转换过的副本,为什么不按值传递并转换已经构建的临时副本?

std::string ToUpper( std::string source )
{
    std::transform( source.begin(), source.end(), source.begin(), ::toupper );
    return source;
}

答案 6 :(得分:0)

第一个有复制到空字符串的错误(如上所述)。另一种解决方法是使用back_inserter(result)

鉴于此,第一个版本似乎略快,因为它没有将初始化值复制到结果中;然而,有一个问题是常规插入的效率(如2)与通过back_inserter插入的效率,即push_back(如1)。

一般来说,我建议你更喜欢你认为更好的风格,并且在这一点上对这种细粒度优化感到困扰,直到你有一个真实的证据表明你有性能问题,这可能成为瓶颈之一。预先分配结果就足够了。 Beware Premature Optimization

答案 7 :(得分:-2)

第一个更好......第二个是错误的,使得字符设置为NUL显得很重要。