std::字符串和多个串联

std::string and multiple concatenations

本文关键字:字符串 std      更新时间:2023-10-16

让我们考虑这个片段,并假设a、b、c和d是非空字符串。

    std::string a, b, c, d;
    d = a + b + c;

当计算这3个std::string实例的总和时,标准库实现创建第一个临时std::string对象,在其内部缓冲区中复制ab的级联缓冲区,然后在临时字符串和c之间执行相同的操作。

一位程序员同事强调,operator+(std::string, std::string)可以被定义为返回std::string_helper,而不是这种行为。

这个对象的作用是将实际的连接推迟到它被广播到std::string中的那一刻。显然,operator+(std::string_helper, std::string)将被定义为返回相同的助手,这将"记住"它有一个额外的串联要执行的事实。

这样的行为可以节省创建n-1个临时对象、分配缓冲区、复制它们等的CPU成本。所以我的问题是:为什么它还没有这样工作?我想不出任何缺点或限制。

为什么它还没有这样工作?

我只能推测为什么它最初是这样设计的。也许字符串库的设计者根本没有想到;也许他们认为在某些情况下,额外的类型转换(见下文)可能会使行为过于令人惊讶。它是最古老的C++库之一,许多我们认为理所当然的智慧在过去几十年里根本不存在。

至于为什么它没有被更改为这样工作:它可以通过添加额外的用户定义类型转换来破坏现有代码。隐式转换最多只能涉及一个用户定义的转换。这是由C++113.3.3.1.2/1:规定的

用户定义的转换序列包括一个初始标准转换序列,然后是一个用户定义转换,然后是第二个标准转换序列。

考虑以下内容:

struct thingy {
    thingy(std::string);
};
void f(thingy);
f(some_string + another_string);

如果some_string + another_string的类型为std::string,则此代码可以。可以通过转换构造函数将其隐式转换为thingy。然而,如果我们要更改operator+的定义以给出另一种类型,那么它将需要两次转换(string_helperstringthingy),因此无法编译。

因此,如果字符串构建的速度很重要,则需要使用其他方法,如+=的串联。或者,根据Matthieu的回答,不要担心,因为C++11以不同的方式解决了效率低下的问题。

显而易见的答案是:因为标准不允许这样做。在某些情况下,它通过引入额外的用户定义转换来影响代码:如果C是一个用户定义构造函数采用std::string的类型,那么它将使:

C obj = stringA + stringB;

非法。

这取决于情况。

在C++03中,确切地说,可能有一点效率低下(顺便说一句,与Java和C#相当,因为它们使用字符串内部处理)。使用可以缓解这种情况

d = std::string("") += a += b +=c;

这并不是真的。。。惯用的

在C++11中,operator+被重载以用于右值引用。意思是:

d = a + b + c;

转换为:

d.assign(std::move(operator+(a, b).append(c)));

这(几乎)是你能得到的最有效的。

C++11版本中剩下的唯一低效之处是,内存在一开始并没有一次性保留,因此可能会进行重新分配和复制,最多2次(对于每个新字符串)。尽管如此,因为追加是O(1)摊销的,除非C比B长得多,否则最坏的情况下应该进行一次重新分配+复制。当然,我们在这里谈论的是POD复制(所以是memcpy呼叫)。

听起来像这样的东西已经存在:std::stringstream

只有您有<<而不是+。仅仅因为std::string::operator +的存在,并不能使其成为最有效的选择。

我认为如果你使用+=,那么它会更快一点:

d += a;
d += b;
d += c;

它应该更快,因为它不会创建临时对象。或者简单地说,

d.append(a).append(b).append(c); //same as above: i.e using '+=' 3 times.

不进行单个+串联的字符串,尤其是不在循环中进行串联的主要原因是它具有O(n2)复杂性。

对于O(n)复杂性,一个合理的替代方案是使用简单的字符串生成器,如

template< class Char >
class ConversionToString
{
public:
    // Visual C++ 10.0 has some DLL linking problem with other types:
    CPP_STATIC_ASSERT((
        std::is_same< Char, char >::value || std::is_same< Char, wchar_t >::value
        ));
    typedef std::basic_string< Char >           String;
    typedef std::basic_ostringstream< Char >    OutStringStream;
    // Just a default implementation, not particularly efficient.
    template< class Type >
    static String from( Type const& v )
    {
        OutStringStream stream;
        stream << v;
        return stream.str();
    }
    static String const& from( String const& s )
    {
        return s;
    }
};

template< class Char, class RawChar = Char >
class StringBuilder;

template< class Char, class RawChar >
class StringBuilder
{
private:
    typedef std::basic_string< Char >       String;
    typedef std::basic_string< RawChar >    RawString;
    RawString   s_;
    template< class Type >
    static RawString fastStringFrom( Type const& v )
    {
        return ConversionToString< RawChar >::from( v );
    }
    static RawChar const* fastStringFrom( RawChar const* s )
    {
        assert( s != 0 );
        return s;
    }
    static RawChar const* fastStringFrom( Char const* s )
    {
        assert( s != 0 );
        CPP_STATIC_ASSERT( sizeof( RawChar ) == sizeof( Char ) );
        return reinterpret_cast< RawChar const* >( s );
    }
public:
    enum ToString { toString };
    enum ToPointer { toPointer };
    String const&   str() const             { return reinterpret_cast< String const& >( s_ ); }
    operator String const& () const         { return str(); }
    String const& operator<<( ToString )    { return str(); }
    RawChar const*     ptr() const          { return s_.c_str(); }
    operator RawChar const* () const        { return ptr(); }
    RawChar const* operator<<( ToPointer )  { return ptr(); }
    template< class Type >
    StringBuilder& operator<<( Type const& v )
    {
        s_ += fastStringFrom( v );
        return *this;
    }
};
template< class Char >
class StringBuilder< Char, Char >
{
private:
    typedef std::basic_string< Char >   String;
    String  s_;
    template< class Type >
    static String fastStringFrom( Type const& v )
    {
        return ConversionToString< Char >::from( v );
    }
    static Char const* fastStringFrom( Char const* s )
    {
        assert( s != 0 );
        return s;
    }
public:
    enum ToString { toString };
    enum ToPointer { toPointer };
    String const&   str() const             { return s_; }
    operator String const& () const         { return str(); }
    String const& operator<<( ToString )    { return str(); }
    Char const*     ptr() const             { return s_.c_str(); }
    operator Char const* () const           { return ptr(); }
    Char const* operator<<( ToPointer )     { return ptr(); }
    template< class Type >
    StringBuilder& operator<<( Type const& v )
    {
        s_ += fastStringFrom( v );
        return *this;
    }
};
namespace narrow {
    typedef StringBuilder<char>     S;
}  // namespace narrow
namespace wide {
    typedef StringBuilder<wchar_t>  S;
}  // namespace wide

然后你可以写出高效清晰的东西,比如…

using narrow::S;
std::string a = S() << "The answer is " << 6*7;
foo( S() << "Hi, " << username << "!" );