StringBuffer / StringBuilder와 동등한 C ++?


184

C #의 StringBuilder 또는 Java의 StringBuffer 와 유사한 효율적인 문자열 연결 기능을 제공하는 C ++ 표준 템플릿 라이브러리 클래스가 있습니까?


3
짧은 대답은 다음과 같습니다. 예, STL에는 해당 클래스가 std::ostringstream있습니다.
CoffeDeveloper

안녕하세요 @andrew. 허용 된 답변을 변경할 수 있습니까? 명확한 답이 있으며 현재 허용되는 답변이 아닙니다.
null

답변:


53

이 답변은 최근에 주목을 받았습니다. 나는 이것을 솔루션으로 옹호하지 않고있다 (STL 이전에 내가 본 솔루션이다). 그것은 흥미로운 접근 방식 만 이상 적용되어야 std::string또는 std::stringstream코드를 프로파일 링 후 발견 할 경우이 개선한다.

일반적으로 std::string또는을 사용합니다 std::stringstream. 나는 이것들에 아무런 문제가 없었습니다. 나는 끈의 거친 크기를 미리 알고 있다면 일반적으로 먼저 방을 예약 할 것입니다.

나는 다른 사람들이 먼 옛날 자신의 최적화 된 스트링 빌더를 만드는 것을 보았습니다.

class StringBuilder {
private:
    std::string main;
    std::string scratch;

    const std::string::size_type ScratchSize = 1024;  // or some other arbitrary number

public:
    StringBuilder & append(const std::string & str) {
        scratch.append(str);
        if (scratch.size() > ScratchSize) {
            main.append(scratch);
            scratch.resize(0);
        }
        return *this;
    }

    const std::string & str() {
        if (scratch.size() > 0) {
            main.append(scratch);
            scratch.resize(0);
        }
        return main;
    }
};

문자열의 대부분을위한 두 개의 문자열과 짧은 문자열을 연결하기위한 스크래치 영역으로 두 개의 문자열을 사용합니다. 하나의 작은 문자열로 짧은 추가 작업을 일괄 처리 한 다음 기본 문자열에 추가하여 기본 문자열에 필요한 재 할당 횟수를 줄임으로써 추가를 최적화합니다.

나는 함께이 트릭을 요구하지 않은 std::stringstd::stringstream. std :: string 전에 타사 문자열 라이브러리와 함께 사용되었다고 생각합니다. 오래 전에였습니다. 이 프로필과 같은 전략을 채택하면 먼저 응용 프로그램을 사용하십시오.


13
바퀴를 재발 명. std :: stringstream이 정답입니다. 아래에서 좋은 답변을보십시오.
Kobor42

13
@ Kobor42 나는 내 대답의 첫 번째와 마지막 줄을 지적하면서 당신에게 동의합니다.
iain

1
나는 scratch문자열이 실제로 여기에서 아무것도 성취 하지 못한다고 생각합니다 . 기본 문자열의 재 할당 횟수는 string구현이 실제로 열악하지 않은 한 (즉, 지수 증가를 사용하지 않는 한) 추가 작업의 수가 아니라 최종 크기의 함수가 될 것 입니다. 따라서 append일단 "배치"하는 것은 도움 이 되지 않습니다. 왜냐하면 일단 기본 string이 커지면 때때로 어느 쪽이든 커질 것입니다. 또한 중복 복사 작업 을 많이 추가하고 짧은 문자열을 추가하기 때문에 더 많은 재 할당 (따라서 new/ 호출)이 발생할 수 있습니다 delete.
BeeOnRope

@BeeOnRope 동의합니다.
iain

나는 str.reserve(1024);이것보다 더 빠를 것이라고 확신 한다
hanshenrik

160

C ++ 방식은 std :: stringstream 또는 일반 문자열 연결 을 사용하는 것 입니다. C ++ 문자열은 변경 가능하므로 연결 성능 ​​고려 사항은 그다지 중요하지 않습니다.

형식화와 관련하여 스트림에서 모두 동일한 형식을 수행 할 수 있지만와 비슷한 방식으로 다른 방식으로cout 수행 할 수 있습니다 . 또는 이것을 캡슐화하고 String : Format 과 같은 인터페이스를 제공하는 강력한 유형의 functor를 사용할 수 있습니다 : 예 : boost :: format


59
C ++ 문자열은 변경 가능 합니다. 전체적인 이유 는 Java의 불변 기본 문자열 유형의 비 효율성StringBuilder다루기위한 것 입니다. 다시 말해 StringBuilder패치 워크이므로 C ++에서 이러한 클래스가 필요하지 않다는 점이 기쁘다.
bobobobo

57
@bobobobo 불변 문자열은 코스의 말
jk

8
일반 문자열 연결이 새 객체를 생성하지 않으므로 Java의 불변성과 동일한 문제가 있습니까? 다음 예제에서 모든 변수가 문자열이라고 가정하십시오. a = b + c + d + e + f; b와 c에서 operator +를 호출 한 다음 결과와 d 등에서 operator +를 호출하지 않습니까?
Serge Rogatch

9
잠깐 동안 사람들을 붙잡고 표준 문자열 클래스는 자신을 돌연변이시키는 방법을 알고 있지만 비 효율성이 없다는 것을 의미하지는 않습니다. 내가 아는 한 std :: string은 단순히 내부 char *의 크기를 확장 할 수 없습니다. 즉, 더 많은 문자가 필요한 방식으로 변경하면 재 할당 및 복사가 필요합니다. 그것은 문자 벡터와 다르지 않으며 그 경우에 필요한 공간을 확보하는 것이 좋습니다.
Trygve Skogsholm

7
@TrygveSkogsholm-문자 벡터와 다르지 않지만 물론 문자열의 "용량"이 크기보다 클 수 있으므로 모든 추가에 재 할당이 필요한 것은 아닙니다. 일반적으로 문자열은 지수 성장 전략을 사용하므로 선형 비용 작업에 여전히 암모를 추가합니다. 모든 추가 작업이 두 문자열의 모든 문자를 새 문자열로 복사해야하는 Java의 불변 문자열과는 다르므로 일련의 추가는 O(n)일반적으로 끝납니다 .
BeeOnRope

93

std::string.append함수는 많은 형식의 데이터를 받아들이지 않기 때문에 좋은 옵션이 아닙니다. 더 유용한 대안은 사용하는 것입니다 std::stringstream. 이렇게 :

#include <sstream>
// ...

std::stringstream ss;

//put arbitrary formatted data into the stream
ss << 4.5 << ", " << 4 << " whatever";

//convert the stream buffer into a string
std::string str = ss.str();

43

std::string 이다 는 C ++ 상당 : 그것은 변경할 수 있습니다.


13

단순히 문자열을 연결하기 위해 .append ()를 사용할 수 있습니다.

std::string s = "string1";
s.append("string2");

나는 당신이 할 수 있다고 생각합니다.

std::string s = "string1";
s += "string2";

C #의 형식화 작업에 관해서 StringBuildersnprintf(또는 sprintf버그가있는 코드 ;-) 쓰기 위험이있는 경우 문자 배열로 변환하고 문자열로 다시 변환하는 것이 유일한 옵션이라고 생각합니다.


printf 또는 .NET의 String.Format과 같은 방식이 아닌가?
Andy Shellam

1
비록 그들이 유일한 방법이라고 말하는 것은 약간 불쾌하다
jk.

2
@ jk-.NET의 StringBuilder의 형식 지정 기능을 비교할 때 유일한 방법입니다. 원래 질문에서 구체적으로 묻는 것입니다. "믿습니다"라고 말하면 틀릴 수 있지만 printf를 사용하지 않고 C ++에서 StringBuilder의 기능을 얻는 방법을 보여줄 수 있습니까?
Andy Shellam

내 대답을 업데이트하는 것은 몇 가지 다른 서식 옵션을 포함하는
JK합니다.

6

std::stringC ++에서는 변경 가능 하므로 사용할 수 있습니다. 그것은이 += operatorappend기능.

숫자 데이터를 추가해야하는 경우 std::to_string기능을 사용하십시오 .

객체를 문자열로 직렬화 할 수있는 형태로 더 많은 유연성을 원한다면 std::stringstream클래스 를 사용하십시오 . 그러나 자체 사용자 정의 클래스와 작동하려면 자체 스트리밍 연산자 기능을 구현해야합니다.


4

std :: string 's + =는 const char * ( "string to add"와 같은 것)와 함께 작동하지 않으므로 stringstream을 사용하는 것이 가장 필요한 것에 가장 가깝습니다. + 대신 <<를 사용하십시오.


3

C ++를위한 편리한 문자열 빌더

많은 사람들이 이전에 대답했듯이 std :: stringstream이 선택 방법입니다. 잘 작동하며 많은 변환 및 서식 옵션이 있습니다. 그러나 IMO에는 하나의 불편한 결함이 있습니다. 하나의 라이너 또는 표현식으로 사용할 수 없습니다. 항상 다음과 같이 작성해야합니다.

std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );

생성자에서 문자열을 초기화하려는 경우 특히 성가시다.

그 이유는 a) std :: stringstream에는 std :: string으로의 변환 연산자가없고 b) stringstream의 연산자 << ()는 문자열 스트림 참조를 반환하지 않지만 대신 std :: ostream 참조를 반환하기 때문입니다. -문자열 스트림으로 더 이상 계산할 수 없습니다.

해결책은 std :: stringstream을 재정의하고 더 나은 일치 연산자를 제공하는 것입니다.

namespace NsStringBuilder {
template<typename T> class basic_stringstream : public std::basic_stringstream<T>
{
public:
    basic_stringstream() {}

    operator const std::basic_string<T> () const                                { return std::basic_stringstream<T>::str();                     }
    basic_stringstream<T>& operator<<   (bool _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (char _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (signed char _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned char _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (short _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned short _val)                   { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (int _val)                              { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned int _val)                     { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long long _val)                        { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long long _val)               { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (float _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (double _val)                           { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long double _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (void* _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::streambuf* _val)                  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ostream& (*_val)(std::ostream&))  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios& (*_val)(std::ios&))          { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios_base& (*_val)(std::ios_base&)){ std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (const T* _val)                         { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val)); }
    basic_stringstream<T>& operator<<   (const std::basic_string<T>& _val)      { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val.c_str())); }
};

typedef basic_stringstream<char>        stringstream;
typedef basic_stringstream<wchar_t>     wstringstream;
}

이것으로, 당신은 같은 것을 쓸 수 있습니다

std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )

생성자에서도.

나는 문자열 작성을 많이 사용하는 환경에서 성능을 사용하지 않았기 때문에 성능을 측정하지 않았다고 고백해야하지만 모든 것이 완료되었으므로 std :: stringstream보다 훨씬 나쁘지 않을 것이라고 가정합니다. 참조를 통해 (문자열로의 변환은 제외하지만 std :: stringstream의 복사 작업)


깔끔합니다. 왜 std::stringstream이런 식으로 행동하지 않는지 모르겠습니다 .
einpoklum

1

로프 대상 문자열의 임의 위치에 또는 긴 문자의 시퀀스 / 삭제 문자열을 삽입해야하는 경우 컨테이너는 가치가있을 수 있습니다. 다음은 SGI 구현의 예입니다.

crope r(1000000, 'x');          // crope is rope<char>. wrope is rope<wchar_t>
                                // Builds a rope containing a million 'x's.
                                // Takes much less than a MB, since the
                                // different pieces are shared.
crope r2 = r + "abc" + r;       // concatenation; takes on the order of 100s
                                // of machine instructions; fast
crope r3 = r2.substr(1000000, 3);       // yields "abc"; fast.
crope r4 = r2.substr(1000000, 1000000); // also fast.
reverse(r2.mutable_begin(), r2.mutable_end());
                                // correct, but slow; may take a
                                // minute or more.

0

다음과 같은 이유로 새로운 것을 추가하고 싶었습니다.

첫 시도에서 나는 이길 수 없었다

std::ostringstream '에스 operator<<

효율성, 그러나 더 많은 시도와 함께 경우에 따라 더 빠른 StringBuilder를 만들 수있었습니다.

문자열을 추가 할 때마다 참조를 저장하고 총 크기의 카운터를 늘립니다.

내가 마지막으로 구현 한 실제 방법 (Horror!)은 불투명 버퍼 (std :: vector <char>)를 사용하는 것입니다.

  • 1 바이트 헤더 (다음 데이터가 다음과 같은 데이터인지를 알려주는 2 비트 : 이동 된 문자열, 문자열 또는 바이트 [])
  • 바이트의 길이를 알려주는 6 비트 []

바이트 []

  • 짧은 문자열의 바이트를 직접 저장합니다 (순차적 메모리 액세스를 위해)

이동 한 문자열 (으로 추가 된 문자열 std::move)

  • std::string객체에 대한 포인터 (소유권 보유)
  • 사용되지 않은 예약 바이트가 있으면 클래스에 플래그를 설정하십시오.

문자열

  • std::string객체에 대한 포인터 (소유권 없음)

마지막으로 삽입 된 문자열이 이동 된 경우 하나의 작은 최적화가 있습니다. 무료 예약되었지만 사용되지 않은 바이트를 확인하고 불투명 버퍼를 사용하는 대신 추가 바이트를 저장합니다 (메모리를 절약하기 위해 실제로 약간 느립니다) , 아마도 CPU에 따라 다를 수 있으며 여분의 여유 공간이있는 문자열은 거의 볼 수 없습니다)

이것은 마침내 약간 빠르지 std::ostringstream만 단점은 거의 없습니다.

  • 고정 길이 문자 유형 (1, 2 또는 4 바이트, UTF8에는 적합하지 않음)을 가정했지만 UTF8에서는 작동하지 않는다고 말하지는 않고 게으름을 확인하지 않았습니다.
  • 나는 나쁜 코딩 연습을 사용했습니다 (불투명 버퍼, 휴대하기 쉽지 않음, 내 방식은 휴대용이라고 생각합니다)
  • 모든 기능 부족 ostringstream
  • 모든 참조 문자열을 합치기 전에 일부 참조 된 문자열이 삭제 된 경우 : 정의되지 않은 동작.

결론? 사용하다 std::ostringstream

이미 가장 큰 병목 현상을 해결하는 동시에 광산 구현으로 몇 %의 속도를내는 것은 단점이 아닙니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.