Abseil Tip 163 std::optional 매개변수 전달하기

주간 팁 #163: <code>std::optional</code> 매개변수 전달하기

원래 TotW #163으로 2019년 7월 11일 게시됨
작성자: Ian Eldred Pudney
2020-04-06 업데이트됨

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

Null 참조는 정말로 10억 달러의 실수일까요?


문제

매개변수가 존재할 수도 있고 존재하지 않을 수도 있는 함수를 구현해야 한다고 가정해봅시다. 이 경우, 현대적인 std::optional을 사용하고 싶은 유혹을 받을 수 있습니다. 그러나 객체가 충분히 커서 참조로 전달해야 한다면, std::optional은 적합하지 않을 수 있습니다. 다음 두 선언을 살펴보세요:

void MyFunc(const std::optional<Foo>& foo);  // 값 복사 가능성 있음
void MyFunc(std::optional<const Foo&> foo);  // 컴파일되지 않음

첫 번째 옵션은 아마도 원하는 동작을 수행하지 않을 것입니다. 만약 누군가 std::optional<Foo>MyFunc에 전달하면 참조로 전달되지만, 누군가 Foo를 (예: 반환 값으로) 전달하면, Foo가 임시 std::optional<Foo>로 값 복사된 후 함수에 참조로 전달됩니다. 복사를 피하려고 했던 목적이 달성되지 않는 것이죠.

두 번째 옵션은 훌륭할 것 같지만, 불행히도 std::optional에서는 지원되지 않습니다.


권장 사항

const std::optional& 형태의 함수 매개변수를 피하세요.

객체가 참조로 전달할 만큼 크지 않은 경우, std::optional로 감싼 객체를 값으로 전달하세요. 예를 들어:

void MyFunc(std::optional<int> bar);
void MyFunc(std::optional<absl::string_view> baz);

매개변수의 복사를 의도적으로 수행하는 경우에도 std::optional을 값으로 전달하여 이를 명확히 하세요:

void MyFunc(std::optional<Foo> foo);

그 외의 경우, std::optional을 완전히 생략하세요. 대신 absl::Nullable<const Foo*>를 사용하고, nullptr로 “존재하지 않음”을 나타낼 수 있습니다.

void MyFunc(absl::Nullable<const Foo*> foo);

이 방식은 const Foo&로 전달하는 것만큼 효율적이며 null 값을 지원합니다. 포인터 대신 const 참조를 사용할 때에 대한 자세한 내용은 팁 #116을 참조하세요.


그렇다면 도대체 std::optional은 어디에 사용해야 할까요?

std::optional은 선택적인 객체를 소유해야 하는 경우에 사용할 수 있습니다. 예를 들어, 클래스 멤버 또는 함수 반환 값에 적합합니다.


예외 사항

모든 호출자가 이미 std::optional<Foo>를 가지고 있고 절대 Foo만 전달하지 않을 것으로 예상되는 경우, const std::optional<Foo>&를 사용할 수 있습니다. 그러나 이는 드문 경우로, 일반적으로 함수가 파일/라이브러리 내부에서만 사용하는 private 함수일 때만 해당됩니다.


std::reference_wrapper는 어떨까요?

std::optional 문서는 optional 참조를 지원하지 않는 문제를 해결하기 위해 std::reference_wrapper를 사용할 수 있다고 언급합니다:

void MyFunc(std::optional<std::reference_wrapper<const Foo>> foo);

그러나, 우리는 이를 권장하지 않습니다:

  • std::reference_wrapper는 놀랍도록 미묘한 동작 방식을 가지며, 이해하고 안전하게 사용하기 어렵습니다. 예를 들어, 표준 라이브러리의 다양한 유틸리티는 이를 특별히 처리하여 일반 값 또는 참조와 다르게 동작하게 합니다.
  • std::optional<std::reference_wrapper<const Foo>>absl::Nullable<const Foo*>에 비해 장황하고 사용하기 불편합니다.

결론적으로, 함수 매개변수로 std::optional을 사용할 때는 상황에 맞게 신중하게 선택해야 합니다.