C #의 StringBuilder 또는 Java의 StringBuffer 와 유사한 효율적인 문자열 연결 기능을 제공하는 C ++ 표준 템플릿 라이브러리 클래스가 있습니까?
C #의 StringBuilder 또는 Java의 StringBuffer 와 유사한 효율적인 문자열 연결 기능을 제공하는 C ++ 표준 템플릿 라이브러리 클래스가 있습니까?
답변:
이 답변은 최근에 주목을 받았습니다. 나는 이것을 솔루션으로 옹호하지 않고있다 (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 전에 타사 문자열 라이브러리와 함께 사용되었다고 생각합니다. 오래 전에였습니다. 이 프로필과 같은 전략을 채택하면 먼저 응용 프로그램을 사용하십시오.
scratch
문자열이 실제로 여기에서 아무것도 성취 하지 못한다고 생각합니다 . 기본 문자열의 재 할당 횟수는 string
구현이 실제로 열악하지 않은 한 (즉, 지수 증가를 사용하지 않는 한) 추가 작업의 수가 아니라 최종 크기의 함수가 될 것 입니다. 따라서 append
일단 "배치"하는 것은 도움 이 되지 않습니다. 왜냐하면 일단 기본 string
이 커지면 때때로 어느 쪽이든 커질 것입니다. 또한 중복 복사 작업 을 많이 추가하고 짧은 문자열을 추가하기 때문에 더 많은 재 할당 (따라서 new
/ 호출)이 발생할 수 있습니다 delete
.
str.reserve(1024);
이것보다 더 빠를 것이라고 확신 한다
C ++ 방식은 std :: stringstream 또는 일반 문자열 연결 을 사용하는 것 입니다. C ++ 문자열은 변경 가능하므로 연결 성능 고려 사항은 그다지 중요하지 않습니다.
형식화와 관련하여 스트림에서 모두 동일한 형식을 수행 할 수 있지만와 비슷한 방식으로 다른 방식으로cout
수행 할 수 있습니다 . 또는 이것을 캡슐화하고 String : Format 과 같은 인터페이스를 제공하는 강력한 유형의 functor를 사용할 수 있습니다 : 예 : boost :: format
StringBuilder
을 다루기위한 것 입니다. 다시 말해 StringBuilder
패치 워크이므로 C ++에서 이러한 클래스가 필요하지 않다는 점이 기쁘다.
O(n)
일반적으로 끝납니다 .
이 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();
단순히 문자열을 연결하기 위해 .append ()를 사용할 수 있습니다.
std::string s = "string1";
s.append("string2");
나는 당신이 할 수 있다고 생각합니다.
std::string s = "string1";
s += "string2";
C #의 형식화 작업에 관해서 StringBuilder
는 snprintf
(또는 sprintf
버그가있는 코드 ;-) 쓰기 위험이있는 경우 문자 배열로 변환하고 문자열로 다시 변환하는 것이 유일한 옵션이라고 생각합니다.
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
이런 식으로 행동하지 않는지 모르겠습니다 .
로프 대상 문자열의 임의 위치에 또는 긴 문자의 시퀀스 / 삭제 문자열을 삽입해야하는 경우 컨테이너는 가치가있을 수 있습니다. 다음은 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<<
효율성, 그러나 더 많은 시도와 함께 경우에 따라 더 빠른 StringBuilder를 만들 수있었습니다.
문자열을 추가 할 때마다 참조를 저장하고 총 크기의 카운터를 늘립니다.
내가 마지막으로 구현 한 실제 방법 (Horror!)은 불투명 버퍼 (std :: vector <char>)를 사용하는 것입니다.
바이트 []
이동 한 문자열 (으로 추가 된 문자열 std::move
)
std::string
객체에 대한 포인터 (소유권 보유)문자열
std::string
객체에 대한 포인터 (소유권 없음)마지막으로 삽입 된 문자열이 이동 된 경우 하나의 작은 최적화가 있습니다. 무료 예약되었지만 사용되지 않은 바이트를 확인하고 불투명 버퍼를 사용하는 대신 추가 바이트를 저장합니다 (메모리를 절약하기 위해 실제로 약간 느립니다) , 아마도 CPU에 따라 다를 수 있으며 여분의 여유 공간이있는 문자열은 거의 볼 수 없습니다)
이것은 마침내 약간 빠르지 std::ostringstream
만 단점은 거의 없습니다.
ostringstream
결론? 사용하다
std::ostringstream
이미 가장 큰 병목 현상을 해결하는 동시에 광산 구현으로 몇 %의 속도를내는 것은 단점이 아닙니다.
std::ostringstream
있습니다.