Abseil Tip 188 스마트 포인터를 함수 매개변수로 사용할 때 주의하세요

원문 게시: 2020년 12월 10일, 주간 팁 #188

작성자: Krzysztof Kosiński

최종 수정: 2020-12-10

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

이 코드의 문제는 무엇일까요?

bool CanYouPetTheDog(const std::shared_ptr<Dog>& dog,
                     absl::Duration min_delay) {
  return dog->GetLastPetTime() + min_delay < absl::Now();
}

CanYouPetTheDog 함수는 dog 매개변수의 소유권을 변경하지 않습니다. 그럼에도 불구하고, 함수의 시그니처는 dog가 반드시 std::shared_ptr에 저장되어야 함을 요구합니다. 이는 함수의 동작에 필요하지 않은 특정 소유권 모델에 대한 불필요한 의존성을 초래합니다. 이러한 의존성은 호출자가 std::unique_ptr과 같은 다른 소유권 모델을 사용하거나 객체를 스택에 생성하는 것을 방해합니다.

소유권이 변경되지 않는 경우에는 참조 또는 포인터를 사용하세요

참조를 사용하면 특정 소유권 모델에 대한 의존성을 제거할 수 있으며, 함수가 어떤 Dog 객체든 사용할 수 있도록 허용됩니다.

bool CanYouPetTheDog(const Dog& dog, absl::Duration min_delay) {
  return dog.GetLastPetTime() + min_delay < absl::Now();
}

위와 같이 정의하면 호출자는 어떤 소유권 모델을 사용하든 함수 호출이 가능합니다.

Dog stack_dog;
if (CanYouPetTheDog(stack_dog, delay)) { ... }

auto heap_dog = std::make_unique<Dog>();
if (CanYouPetTheDog(*heap_dog, delay)) { ... }

CustomPetPtr<Dog> custom_dog = CreateDog();
if (CanYouPetTheDog(*custom_dog, delay)) { ... }

함수가 전달된 값을 수정해야 한다면, 수정 가능한 참조 또는 원시 포인터를 사용하고 위와 동일한 방식을 따르세요.

소유권을 수정하는 경우에는 스마트 포인터를 사용하세요

다음 코드는 스마트 포인터 매개변수에 대해 여러 오버로드를 제공합니다. 첫 번째 오버로드는 전달된 객체의 소유권을 가져오고, 두 번째는 전달된 객체에 대해 공유 참조를 추가합니다. 이러한 작업은 호출자가 Dog의 소유권을 어떻게 처리하는지에 따라 달라집니다. 스택에 존재하는 Dog의 소유권은 가져올 수 없으므로 스택 객체는 이러한 방식으로 채택될 수 없습니다.

class Human {
 public:
  ...
  // `dog`의 소유권을 이 Human 객체로 이전합니다.
  // std::unique_ptr를 값으로 받는 이유는 팁 #117을 참고하세요.
  void Adopt(std::unique_ptr<Dog> dog) {
    pets_.push_back(std::move(dog));
  }
  // `cat`에 대해 공유 참조를 추가합니다.
  void Adopt(std::shared_ptr<Cat> cat) {
    pets_.push_back(std::move(cat));
  }

 private:
  std::vector<std::shared_ptr<Pet>> pets_;
  ...
};

결론

소유권이 이전되거나 수정되지 않는 경우, 함수 매개변수로 스마트 포인터를 사용하는 것을 피하세요.

참고 자료