콘텐츠로 이동

암시적 변환 (Implicit Conversions)

암시적 변환을 정의하지 마세요. 변환 연산자와 단일 인수 생성자에는 명시적 키워드를 사용합니다.

암시적 변환을 사용하면 이중 매개변수를 사용하는 함수에 int 인수를 전달할 때와 같이 다른 유형(대상 유형이라고 함)이 예상되는 위치에서 한 가지 유형(소스 유형이라고 함)의 객체를 사용할 수 있습니다.

언어에 의해 정의된 암시적 변환 외에도 사용자는 소스 또는 대상 유형의 클래스 정의에 적절한 멤버를 추가하여 자신만의 변환을 정의할 수 있습니다. 소스 유형의 암시적 변환은 대상 유형의 이름을 딴 유형 변환 연산자(예: operator bool())에 의해 정의됩니다. 대상 유형의 암시적 변환은 소스 유형을 유일한 인수(또는 기본값이 없는 유일한 인수)로 사용할 수 있는 생성자에 의해 정의됩니다.

명시적 키워드는 생성자 또는 변환 연산자에 적용하여 사용 시점에서 대상 유형이 명시적인 경우(예: 캐스트 등)에만 사용할 수 있도록 할 수 있습니다. 이는 암시적 변환뿐만 아니라 목록 초기화 구문에도 적용됩니다.

class Foo {
  explicit Foo(int x, double y);
  ...
};

void Func(Foo f);
Func({42, 3.14});  // Error

이러한 종류의 코드는 기술적으로는 암시적 변환이 아니지만 언어에서는 이를 명시적인 변환으로 처리합니다.

  • 암시적 변환을 사용하면 유형이 분명할 때 명시적으로 이름을 지정할 필요가 없으므로 유형을 더욱 유용하고 표현력 있게 만들 수 있습니다.
  • 암시적 변환은 string_view 매개변수가 있는 단일 함수가 std::string 및 const char* 에 대한 별도의 오버로드를 대신하는 경우처럼 오버로드에 대한 더 간단한 대안이 될 수 있습니다.
  • 목록 초기화 구문은 객체를 초기화하는 간결하고 표현적인 방법입니다.

  • 암시적 변환은 대상 유형이 사용자의 기대와 일치하지 않거나 사용자가 변환이 발생한다는 것을 인식하지 못하는 유형 불일치 버그를 숨길 수 있습니다.

  • 암시적 변환은 실제로 어떤 코드가 호출되는지 명확하지 않게 만들어 특히 오버로드가 있는 경우 코드를 읽기 어렵게 만들 수 있습니다.
  • 단일 인수를 취하는 생성자는 의도하지 않았더라도 우연히 암시적 유형 변환으로 사용될 수 있습니다.
  • 단일 인수 생성자가 명시적으로 표시되지 않은 경우 암시적 변환을 정의하려는 것인지 또는 작성자가 표시하는 것을 잊어버렸는지 여부를 알 수 있는 신뢰할 수 있는 방법이 없습니다.
  • 암시적 변환은 특히 양방향 암시적 변환이 있는 경우 호출 사이트 모호성을 초래할 수 있습니다. 이는 암시적 변환을 제공하는 두 가지 형식이 있거나 암시적 생성자와 암시적 형식 변환 연산자가 모두 있는 단일 형식으로 인해 발생할 수 있습니다.
  • 대상 유형이 암시적인 경우, 특히 목록에 단일 요소만 있는 경우 목록 초기화에서 동일한 문제가 발생할 수 있습니다.

단일 인수로 호출할 수 있는 유형 변환 연산자 및 생성자는 클래스 정의에서 명시적으로 표시되어야 합니다. 예외적으로 복사 및 이동 생성자는 형식 변환을 수행하지 않으므로 명시적이어서는 안 됩니다.

암시적 변환은 때때로 상호 교환 가능하도록 설계된 유형에 필요하고 적합할 수 있습니다. 예를 들어 두 유형의 객체가 동일한 기본 값을 다르게 표현한 경우입니다. 이 경우 프로젝트 리더에게 연락하여 이 규칙의 면제를 요청하세요.

단일 인수로 호출할 수 없는 생성자는 명시적 을 생략할 수 있습니다. 단일 std::initializer_list 매개변수를 사용하는 생성자는 복사 초기화를 지원하기 위해 명시적 을 생략해야 합니다(예: MyType m = {1, 2}; ).


이해하기 쉽게 설명하기

핵심: 단일 인수 생성자와 변환 연산자에 explicit을 붙여라

암시적 변환은 A 타입을 기대하는 자리에 B 타입을 그냥 넣을 수 있게 해줍니다. 편해 보이지만, 어떤 코드가 실제로 호출되는지 불분명해지고 타입 불일치 버그를 숨깁니다.

class Foo {
 public:
  explicit Foo(int x);  // explicit가 없으면 Foo가 필요한 곳에 int가 슬쩍 변환됨
};

void Func(Foo f);
Func(42);       // explicit면 컴파일 오류 (변환 의도가 드러나도록 강제)
Func(Foo(42));  // 이렇게 명시적으로 써야 함

규칙

  • 단일 인수로 호출 가능한 생성자변환 연산자(operator bool() 등)는 explicit으로 표시하세요.
  • 이는 중괄호 목록 초기화(Func({42, 3.14}))에도 적용됩니다.

예외 (explicit를 붙이지 않음)

  • 복사·이동 생성자: 타입 변환이 아니므로 explicit을 붙이지 않습니다.
  • 단일 인수로 호출할 수 없는 생성자.
  • 단일 std::initializer_list 매개변수 생성자: 복사 초기화(MyType m = {1, 2};)를 지원하려면 explicit을 생략합니다.
  • 서로 교환 가능하도록 설계된 타입(같은 값의 다른 표현)은 암시적 변환이 적절할 수 있으니, 이 경우 프로젝트 리드에게 예외를 요청하세요.