Abseil Tip 177 할당 가능성과 데이터 멤버 타입

주간 팁 #177: 할당 가능성과 데이터 멤버 타입

원래 TotW #177로 2020년 4월 6일 게시됨
작성자: Titus Winters
2020-04-06 업데이트됨

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


타입을 구현할 때는 먼저 타입 설계에 대해 결정하세요. 구현 세부사항보다 API를 우선시하는 것이 중요합니다. 이를 잘 보여주는 일반적인 예는 타입의 할당 가능성과 데이터 멤버의 한정자 간의 트레이드오프입니다.


데이터 멤버를 표현하는 방법 결정

예를 들어, City 클래스를 작성한다고 가정해봅시다. 이 클래스의 멤버 변수를 어떻게 표현할지 논의 중입니다. 이 클래스는 단기적으로 사용되며, 도시의 특정 시점 스냅샷을 나타내므로 population, name, mayor와 같은 값은 변경되지 않는 const로 표현할 수 있을 것 같습니다. 이 클래스는 특정 프로그램에서 수년간 사용되지는 않으므로 인구 변화, 새로운 센서스 결과, 또는 선거와 같은 변경사항을 고려할 필요는 없습니다.

다음과 같이 멤버를 작성해야 할까요?

private:
  const std::string city_name_;
  const Person mayor_;
  const int64_t population_;

왜 그렇게 해야 하나요? 또는 하지 말아야 하나요?

"그 값들은 특정 City 객체에 대해 변경되지 않으므로, 가능한 모든 것은 const로 만들어야 한다"는 의견이 흔히 제기됩니다. 이렇게 하면 클래스의 유지보수자가 이러한 필드를 실수로 수정하지 않도록 돕는다는 것입니다.

그러나 이는 중요한 점을 놓치고 있습니다: City는 어떤 종류의 타입인가요?

  • 이는 값 타입(value type)인가요?
  • 아니면 비즈니스 로직의 묶음인가요?
  • 복사 가능(copyable), 이동 전용(move-only), 또는 비복사 가능(non-copyable)인가요?

City 클래스 전체에서 특정 멤버가 const인지 여부는 효율적으로 작성할 수 있는 연산 집합에 영향을 줄 수 있으며, 이는 종종 나쁜 선택이 됩니다.

특히, 클래스에 const 멤버가 있으면 해당 클래스는 할당(복사 할당 또는 이동 할당)할 수 없습니다. C++ 언어는 이를 이해하고 있습니다: 타입에 const 멤버가 있는 경우, 복사 및 이동 할당 연산자가 자동 생성되지 않습니다. 여전히 객체를 복사(또는 이동) 생성할 수는 있지만, 동일한 타입의 다른 객체에서 값을 복사할 수 없으며, const 멤버를 덮어쓸 수도 없습니다.


참조 멤버

이와 같은 논리는 데이터 멤버로 참조를 저장할 때도 적용됩니다. 예를 들어, 멤버가 항상 null이 아니라고 확신하더라도, 값 타입에서는 참조(T&) 대신 포인터(T*)를 사용하는 것이 일반적으로 더 좋습니다. 참조는 다시 바인딩할 수 없기 때문입니다.

예를 들어, std::vector<T> 구현을 생각해 봅시다. 대부분의 std::vector 구현에는 할당을 가리키는 T* data 멤버가 있을 것입니다. 이 data를 참조로 바꾸는 것은 불가능합니다. std::vector는 값 타입으로, 복사 가능하고 할당 가능해야 합니다. 만약 할당을 참조로 저장한다면, 복사나 이동 할당 시 저장소를 업데이트하는 것이 불가능해집니다. 이로 인해 API 사용에 불편함을 초래합니다.


복사/할당 불가능한 타입

타입 설계에 따라 City가 복사 불가능해야 한다고 결론이 났다면, 구현 제약이 줄어듭니다. 클래스에 const 또는 참조 멤버를 보유하는 것이 반드시 잘못된 것은 아닙니다. 이는 해당 구현 결정이 클래스의 인터페이스를 제한하거나 훼손하지 않는 경우에만 문제입니다.


특이한 사례: 불변 타입

구체적인 상황에서는 불변 타입(immutable type)이 유용할 수 있습니다. 이러한 타입은 생성 후 변경할 수 없습니다: 변경 가능한 메서드나 할당 연산자가 없습니다. 이러한 타입은 본질적으로 스레드 안전하며, 데이터 레이스나 동기화 문제 없이 여러 스레드에서 자유롭게 공유할 수 있습니다. 하지만 이로 인해 객체를 복사해야 하는 오버헤드가 발생하며, 효율적으로 이동하는 것도 불가능합니다.


권장 사항

  • 타입 설계를 구현 세부사항보다 우선적으로 결정하세요.
  • 값 타입비즈니스 로직 타입은 흔히 사용되며, 둘 다 추천됩니다.
  • 불변 타입은 특정 상황에서 유용할 수 있지만, 이러한 경우는 드뭅니다.
  • API 설계와 사용자 요구 사항을 유지보수자의 걱정보다 우선시하세요.
  • 값 타입이나 이동 전용 타입을 설계할 때 const와 참조 멤버를 피하세요.