std :: string和多个连接

时间:2012-03-08 14:59:31

标签: c++ string optimization std

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

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

在计算这3个std::string实例的总和时,标准库实现会创建第一个临时std::string对象,在其内部缓冲区中复制a和{{1}的连接缓冲区然后,在临时字符串和b之间执行相同的操作。

同事程序员强调,c可以定义为operator+(std::string, std::string)而不是此行为。

这个对象的作用是将实际的连接推迟到它被转换为std::string_helper的那一刻。显然,std::string将被定义为返回相同的帮助器,这将“记住”它有一个额外的串联来执行。

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

6 个答案:

答案 0 :(得分:6)

  

为什么它不能像那样工作?

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

至于为什么它没有被改变为这样工作:它可能通过添加额外的用户定义类型转换来破坏现有代码。隐式转换最多只能涉及一个用户定义的转换。这由C ++ 11,13.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以不同的方式修复了低效率。

答案 1 :(得分:6)

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

C obj = stringA + stringB;

非法的。

答案 2 :(得分:4)

取决于。

在C ++ 03中,确实可能存在轻微的低效率(与Java和C#相比,因为它们使用字符串实习)。这可以通过以下方式得到缓解:

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

这不是真的...惯用。

在C ++ 11中,operator+被重载为rvalue引用。意思是:

d = a + b + c;

转变为:

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

(几乎)尽可能高效。

C ++ 11版本中唯一的低效率是内存不是一开始就保留的,所以可能会重新分配并最多复制2次(对于每个新字符串)。仍然,因为追加是摊销O(1),除非C比B长,否则最坏的情况应该发生一次重新分配+复制。当然,我们在这里谈论POD复制(所以memcpy电话)。

答案 3 :(得分:2)

听起来像我这样的事情已经存在:std::stringstream

只有<<代替+。仅仅因为std::string::operator +存在,它并不是最有效的选择。

答案 4 :(得分:0)

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

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

它应该更快,因为它不会创建临时对象。或者只是这个,

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

答案 5 :(得分:0)

不进行单个+串联串联的主要原因,特别是在循环中没有这样做,就是有O( n 2 )复杂性。

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 << "!" );