Abseil Tip 3 문자열 연결과 operator+ vs. StrCat()


title: “이번 주의 팁 #3: 문자열 연결과 operator+ vs. StrCat()” layout: tips sidenav: side-nav-tips.html published: true permalink: tips/3 type: markdown order: “003” —

원래 2012-05-11에 totw/3으로 게시됨

2017-09-18 업데이트; 2018-01-22 수정

리뷰어가 “문자열 연결 연산자를 사용하지 마세요, 비효율적이에요”라고 말했을 때 종종 사용자들은 놀라곤 합니다. std::string::operator+가 비효율적이라니요? 그렇게 구현하기 어렵기라도 한 걸까요?

사실, 이런 비효율성은 명확하지 않을 수 있습니다. 아래 두 코드 조각은 실제 실행 시간에서 거의 동일합니다:

std::string foo = LongString1();
std::string bar = LongString2();
std::string foobar = foo + bar;

std::string foo = LongString1();
std::string bar = LongString2();
std::string foobar = absl::StrCat(foo, bar);

하지만, 아래 두 코드 조각은 결과가 다릅니다:

std::string foo = LongString1();
std::string bar = LongString2();
std::string baz = LongString3();
std::string foobar = foo + bar + baz;

std::string foo = LongString1();
std::string bar = LongString2();
std::string baz = LongString3();
std::string foobar = absl::StrCat(foo, bar, baz);

이 두 경우의 차이를 이해하려면 foo + bar + baz 표현식에서 실제로 어떤 일이 일어나는지 살펴봐야 합니다. C++에서는 세 개의 인수를 처리하는 연산자가 없기 때문에 이 연산은 반드시 두 번의 string::operator+ 호출을 수행해야 합니다. 그리고 이 두 호출 사이에 임시 문자열 객체가 생성되고 저장됩니다. 따라서 std::string foobar = foo + bar + baz는 실제로 다음과 동일합니다:

std::string temp = foo + bar;
std::string foobar = std::move(temp) + baz;

여기서 foobar의 내용은 foobar에 배치되기 전에 임시 위치에 복사되어야 합니다. (더 자세한 내용은 Tip of the Week #77: Temporaries, moves, and copies을 참조하세요.)

C++11에서는 두 번째 연결이 새로운 문자열 객체를 생성하지 않고도 수행될 수 있습니다. 즉, std::move(temp) + bazstd::move(temp.append(baz))와 동일합니다. 하지만 임시 객체에 할당된 버퍼가 최종 문자열을 담기에 충분하지 않을 경우 재할당(그리고 또 다른 복사)이 필요할 수 있습니다. 결과적으로, 최악의 경우에는 n개의 문자열 연결이 O(n)번의 재할당을 필요로 합니다.

이 문제를 해결하기 위해 absl/strings/str_cat.h에서 제공하는 absl::StrCat() 함수를 사용하는 것이 좋습니다. 이 함수는 필요한 문자열 길이를 계산하고, 그 크기를 예약하며, 모든 입력 데이터를 출력 문자열에 결합하는 잘 최적화된 O(n) 함수입니다. 비슷하게, 다음과 같은 경우:

foobar += foo + bar + baz;

absl::StrAppend()를 사용하세요. 이 함수도 유사한 최적화를 수행합니다:

absl::StrAppend(&foobar, foo, bar, baz);

또한, absl::StrCat()absl::StrAppend()는 문자열 타입 외에도 다른 타입에서도 동작합니다. 예를 들어 int32_t, uint32_t, int64_t, uint64_t, float, double, const char*, string_view 등을 처리할 수 있습니다. 아래와 같은 방식으로 사용할 수 있습니다:

std::string foo = absl::StrCat("The year is ", year);