C ++ StringBuffer/StringBuilder와 동등합니까?
-
20-09-2019 - |
문제
C#의와 유사한 효율적인 문자열 연결 기능을 제공하는 C ++ 표준 템플릿 라이브러리 클래스가 있습니까? StringBuilder 또는 Java 's StringBuffer?
해결책
이 답변은 최근에 약간의 관심을 받았습니다. 나는 이것을 해결책으로 옹호하지 않습니다 (그것은 과거에 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::string
또는 std::stringstream
. 나는 그것이 std :: string 전에 타사 문자열 라이브러리와 함께 사용되었다고 생각합니다. 이 프로필과 같은 전략을 채택한 경우 먼저 응용 프로그램을 사용하십시오.
다른 팁
C ++ 방법은 사용하는 것입니다 std :: stringstream 또는 단지 평범한 문자열 연결. C ++ 문자열은 변이 가능하므로 연결의 성능 고려 사항은 문제가되지 않습니다.
서식과 관련하여 스트림에서 동일한 형식을 모두 수행 할 수 있지만 다른 방식으로 cout
. 또는이를 캡슐화하고 문자열을 제공하는 강력하게 입력 된 functor를 사용할 수 있습니다. 인터페이스와 같은 형식 예 : 부스트 :: 형식
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();
std::string
~이다 C ++ 등가 : 변이 가능합니다.
문자열을 연결하는 데 .append ()를 사용할 수 있습니다.
std::string s = "string1";
s.append("string2");
나는 당신이 할 수 있다고 생각합니다.
std::string s = "string1";
s += "string2";
C#의 서식 작업은 StringBuilder
, 나는 믿는다 snprintf
(또는 sprintf
버기 코드를 쓸 위험을 감수하려면 ;-)) 문자 배열로 다시 문자열로 변환하는 것이 유일한 옵션에 관한 것입니다.
부터 std::string
C ++에서는 변이 가능합니다. 그것은 있습니다 += operator
그리고 append
기능.
수치 데이터를 추가 해야하는 경우 사용하십시오 std::to_string
기능.
문자열에 객체를 직렬화 할 수있는 형태로 더 많은 유연성을 원한다면 std::stringstream
수업. 그러나 자신의 커스텀 클래스와 함께 작동하려면 자체 스트리밍 연산자 기능을 구현해야합니다.
std :: string 's +=는 const char* ( "string to add"라는 것과 같은 것들)에서 작동하지 않으므로 StringStream을 사용하는 것은 필요한 것과 가장 가깝습니다. +대신 << <<
C ++의 편리한 문자열 빌더
많은 사람들이 전에 대답했던 것처럼 std :: stringstream이 선택한 방법입니다. 잘 작동하며 많은 변환 및 서식 옵션이 있습니다. IMO는 하나의 꽤 불편한 결함을 가지고 있습니다. 하나의 라이너 또는 표현으로 사용할 수 없습니다. 당신은 항상 다음을 써야합니다.
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
특히 생성자의 문자열을 초기화하려는 경우 꽤 성가시다.
그 이유는 a) std :: stringstream이 std :: string에 대한 변환 연산자가 없기 때문입니다. b) 연산자 << () 's stringstream은 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의 사본 조작입니다)
그만큼 로프 컨테이너는 문자열의 임의의 장소에 문자열을 삽입/삭제 해야하는 경우 또는 긴 숯 시퀀스를 위해 가치가있을 수 있습니다. 다음은 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.
다음으로 인해 새로운 것을 추가하고 싶었습니다.
첫 번째 시도에서 나는 이겼다
std::ostringstream
'에스 operator<<
효율성이지만 더 많은 Attemps를 사용하면 경우에 따라 더 빠른 StringBuilder를 만들 수있었습니다.
문자열을 추가 할 때마다 어딘가에 대한 참조를 저장하고 총 크기의 카운터를 늘립니다.
내가 마침내 그것을 구현 한 진짜 방법 (공포!)은 불투명 버퍼 (std :: vector <char>)를 사용하는 것입니다.
- 1 바이트 헤더 (2 비트 (2 비트)는 다음 데이터가 다음과 같은지 알려줍니다 : 이동 문자열, 문자열 또는 바이트 [])
- 바이트의 Lenght에게 말하는 6 비트 [
바이트 [
- 나는 짧은 줄의 직접 바이트를 저장합니다 (순차적 메모리 액세스 용)
움직이는 현 (문자열이 추가되었습니다 std::move
)
- A에 대한 포인터
std::string
개체 (우리는 소유권이 있습니다) - 미사용 예약 바이트가있는 경우 클래스에 깃발을 설정하십시오.
현악기
- A에 대한 포인터
std::string
개체 (소유권 없음)
마지막으로 삽입 된 문자열이 이동 한 경우 하나의 작은 최적화도 있습니다. 무료 예약되었지만 사용하지 않은 바이트를 확인하고 불투명 버퍼를 사용하는 대신 추가 바이트를 저장합니다 (이것은 일부 메모리를 저장하기 위해서는 실제로 약간 느리게 만듭니다. , 아마도 CPU에도 의존 할 수도 있고 어쨌든 추가 공간이있는 줄을 보는 것은 드 rare니다).
이것은 마침내 약간 더 빠릅니다 std::ostringstream
그러나 단점은 거의 없습니다.
- 나는 고정 된 lenght char 유형 (따라서 1,2 또는 4 바이트, UTF8에 좋지 않음)을 가정했다. 나는 그것이 UTF8에서는 효과가 없다고 말하지 않고 게으름을 확인하지 않았다.
- 나는 나쁜 코딩 연습을 사용했습니다 (불투명 버퍼, 휴대용을 쉽게 만들 수 있습니다. 내 것이 휴대가 가능하다고 생각합니다)
- 모든 기능이 부족합니다
ostringstream
- 일부 문자열이 삭제되면 모든 문자열이 정의되지 않은 동작.
결론? 사용std::ostringstream
이미 가장 큰 병목 현상을 수정하는 반면, 광산 구현으로 속도가 적은 포인트를 늘리는 것은 다운 사이드의 가치가 없습니다.