std :: string и множественные объединения - PullRequest
4 голосов
/ 08 марта 2012

Давайте рассмотрим этот фрагмент и предположим, что a, b, c и d - непустые строки.

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

При вычислении суммы этих 3 std::string экземпляров стандартная библиотека создает первый временный объект std::string, копирует в свой внутренний буфер сцепленные буферы a и b, а затем выполняет те же операции. между временной строкой и c.

Другой программист подчеркивал, что вместо этого поведения можно определить operator+(std::string, std::string) для возврата std::string_helper.

Сама роль этого объекта заключается в том, чтобы отложить фактическую конкатенацию до того момента, когда он преобразуется в std::string. Очевидно, что operator+(std::string_helper, std::string) будет определен так, чтобы возвращать того же помощника, который будет «иметь в виду» тот факт, что он должен выполнить дополнительную конкатенацию.

Такое поведение позволило бы сэкономить затраты ЦП на создание n-1 временных объектов, выделение их буфера, их копирование и т. Д. Итак, мой вопрос: почему это уже не работает? Я не могу думать ни о каких недостаток или ограничение.

Ответы [ 6 ]

6 голосов
/ 08 марта 2012

Очевидный ответ: потому что стандарт не позволяет этого. Он влияет на код, вводя дополнительное пользовательское преобразование в некоторых случаях: если C - это тип, имеющий пользовательский конструктор, принимающий std::string, то он будет иметь:

C obj = stringA + stringB;

незаконна.

6 голосов
/ 08 марта 2012

почему это уже не работает?

Я могу только догадываться о том, почему он был изначально разработан таким образом. Возможно, дизайнеры струнной библиотеки просто не думали об этом; возможно, они думали, что дополнительное преобразование типов (см. ниже) может сделать поведение слишком неожиданным в некоторых ситуациях. Это одна из старейших библиотек 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_helper в string в thingy), и поэтому компиляция не состоялась бы.

Итак, если важна скорость построения строк, вам нужно использовать альтернативные методы, такие как конкатенация с +=. Или, согласно ответу Матье, не беспокойтесь об этом, потому что C ++ 11 исправляет неэффективность другим способом.

4 голосов
/ 08 марта 2012

Это зависит.

В 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 call).

2 голосов
/ 08 марта 2012

Похоже, что что-то подобное уже существует: std::stringstream.

Только у вас есть << вместо +. То, что существует std::string::operator +, не делает его наиболее эффективным вариантом.

0 голосов
/ 08 марта 2012

Основная причина того, что не выполняется строка отдельных + конкатенаций, и особенно это не делается в цикле, заключается в том, что она имеет сложность 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 << "!" );
0 голосов
/ 08 марта 2012

Я думаю, что если вы используете +=, то это будет немного быстрее:

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

Это должно быть быстрее, поскольку не создает временных объектов. Или просто так,

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