Abseil Tip 215 AbslStringify()를 사용한 사용자 정의 타입 문자열화"

제목: “이번 주의 팁 #215: AbslStringify()를 사용한 사용자 정의 타입 문자열화”

원문 게시일: 2022년 11월 2일
업데이트: 2022년 11월 16일

작성자: Phoebe Liang

빠른 링크: abseil.io/tips/215


개요

Abseil은 사용자 정의 타입을 문자열로 포맷할 수 있는 새로운 경량 메커니즘인 AbslStringify()를 지원합니다. AbslStringify()를 확장한 사용자 정의 타입은 absl::StrFormat, absl::StrCat, 그리고 absl::Substitute와 함께 바로 사용할 수 있습니다.

대부분의 타입 확장과 마찬가지로, 확장하려는 타입을 직접 소유하고 있어야 합니다.


사용 예제

간단한 Point 구조체를 예로 들어보겠습니다:

struct Point {
  int x;
  int y;
};

Pointabsl::StrFormat(), absl::StrCat()absl::Substitute()에서 사용하고 싶다면, AbslStringify()라는 friend 함수 템플릿을 추가하면 됩니다:

struct Point {
  template <typename Sink>
  friend void AbslStringify(Sink& sink, const Point& p) {
    absl::Format(&sink, "(%d, %d)", p.x, p.y);
  }

  int x;
  int y;
};

참고: AbslStringify()는 문자열을 생성하기 위해 제네릭 “sink” 버퍼를 사용합니다. 이 sink는 absl::FormatSink와 유사한 인터페이스를 가지지만, PutPaddedString()은 지원하지 않습니다.

이제 absl::StrCat("The point is ", p)absl::Substitute("The point is $0", p)를 사용할 수 있습니다.


%v 타입 지정자와 타입 유추

absl::StrFormat()을 사용해 타입을 포맷하고 싶다면 어떻게 해야 할까요? 기존의 타입 지정자는 AbslStringify()를 확장한 사용자 정의 타입을 지원하지 않기 때문에 다음과 같이 작성해야 합니다:

absl::StrFormat("The point is (%d, %d)", p.x, p.y);

하지만 이는 비효율적입니다. 포맷 문자열을 반복해야 하고 AbslStringify() 확장을 사용하지도 않습니다. 대신 새로운 %v 타입 지정자를 사용할 수 있습니다:

absl::StrFormat("The point is %v", p);

%v는 타입 유추를 통해 인자를 포맷합니다. %v는 대부분의 기본 타입과 AbslStringify()로 확장된 타입을 지원합니다. %v를 사용하면 “값”을 범용적으로 포맷할 수 있습니다.


%v가 유추하는 타입

  • 정수형: d (부호 있는 정수)
  • 부호 없는 정수형: u
  • 부동소수점: g
    • double, float, long double
  • 문자열: s
    • std::string, absl::string_view, std::string_view, absl::Cord

주의: const char*는 지원되지 않습니다.


예제

absl::StrFormat("%v", std::string{"hello"}) -> "hello"
absl::StrFormat("%v", 42) -> "42"
absl::StrFormat("%v", uint64_t{16}) -> "16"
absl::StrFormat("%v", 1.6) -> "1.6"
absl::StrFormat("%v", true) -> "true"

다른 라이브러리와의 통합

로깅 지원

AbslStringify()를 정의한 타입은 바로 로깅에 사용할 수 있습니다:

struct Point {
  template <typename Sink>
  friend void AbslStringify(Sink& sink, const Point& p) {
    absl::Format(&sink, "(%v, %v)", p.x, p.y);
  }

  int x = 10;
  int y = 20;
};

Point p;

LOG(INFO) << p;

이 코드의 로그 출력 결과는 다음과 같습니다:

I0926 09:00:00.000000   12345 main.cc:10] (10, 20)

operator<< 대신 AbslStringify()를 구현하는 것을 권장합니다. 이렇게 하면 absl::StrFormat, absl::StrCat, absl::Substitute에서도 사용할 수 있기 때문입니다.


프로토콜 버퍼 지원

프로토콜 버퍼도 AbslStringify()를 통해 문자열로 포맷할 수 있습니다:

message MyProto {
  optional string my_string = 1;
}

MyProto my_proto;
my_proto.set_my_string("hello world");

absl::StrCat("My proto is: ", my_proto);
absl::StrFormat("My proto is: %v", my_proto);
LOG(INFO) << my_proto;

마무리

%v 타입 지정자는 “그냥 사람이 읽을 수 있는 형태로 출력”하는 것이 목적입니다. 사용자 정의 타입과 같이 필요한 경우를 제외하고는 %v를 간단한 포맷팅에 사용하는 것을 추천합니다. 하지만 더 세부적인 포맷이 필요한 경우에는 기존의 타입 지정자를 사용해야 합니다.