소멸자의 이상한 열거 형


83

현재의 소스 코드를 읽고 있는데 여기에 정의 된 Protocol Buffer이상한 enum코드가 하나 있습니다.

  ~scoped_ptr() {
    enum { type_must_be_complete = sizeof(C) };
    delete ptr_;
  }

  void reset(C* p = NULL) {
    if (p != ptr_) {
      enum { type_must_be_complete = sizeof(C) };
      delete ptr_;
      ptr_ = p;
    }
  }

왜 enum { type_must_be_complete = sizeof(C) };여기에 정의되어 있습니까? 그것은 무엇을 위해 사용됩니까?


2
확실하게하고 싶다면 대신 as ptr_에서 자체를 사용하는 것이 좋습니다 . sizeofsizeof(*ptr_)sizeof(C)
— Nawaz 2015

답변:


81

이 트릭은이 소멸자가 컴파일 될 때 C의 정의를 사용할 수 있도록하여 UB를 방지합니다. 그렇지 않으면 sizeof불완전한 유형 (앞으로 선언 된 유형)을 결정할 수 없지만 포인터를 사용할 수 있으므로 컴파일이 실패합니다 .

컴파일 된 바이너리에서이 코드는 최적화되어 효과가 없습니다.

참고 : 불완전한 유형을 삭제 하는 것은 5.3.5 / 5 :부터 정의되지 않은 동작 일 수 있습니다 .

삭제되는 객체가 삭제 시점에 불완전한 클래스 유형을 가지고 있고 완전한 클래스 에 사소하지 않은 소멸자 또는 할당 해제 함수가있는 경우 동작은 정의되지 않습니다 .

g++ 다음 경고도 발행합니다.

경고 : 삭제 연산자 호출에서 발견 된 가능한 문제 :
경고 : 'p'에 불완전한 유형이 있습니다.
경고 : 'struct C'의 포워드 선언


1
"불완전한 유형 삭제는 정의되지 않은 동작입니다."가 올바르지 않습니다. 유형에 사소하지 않은 소멸자가있는 경우에만 UB입니다. 이 작은 트릭이 해결하는 문제는 불완전한 유형을 삭제하는 것이 항상 UB 가 아니므 로 언어가 지원한다는 것입니다.
— 건배와 hth. - 알프

감사합니다 @ Cheersandhth.-Alf 내 요점은 UB 일 수 있으므로 일반적 으로이 코드 줄은 UB입니다. 편집.
— Mohit Jain

32

sizeof(C)완전한 유형이 아닌 경우 컴파일 타임에 실패 C합니다. 로컬 범위 enum를 설정하면 런타임에 문이 무해하게됩니다.

이것은 프로그래머가 스스로를 보호하는 방법입니다. delete ptr_불완전한 유형에 대한 후속 동작은 사소하지 않은 소멸자가 있으면 정의되지 않습니다.


1
그 시점에서 C가 완전한 유형이어야하는 이유를 설명 할 수 있습니까 delete?이를 호출하기 위해 완전한 유형 정의가 필요합니까? 그렇다면 왜 컴파일러가 그것을 포착하지 못합니까?
— Peter Hull

1
오히려 피하는 것이 아닌가 C = void? 경우 C단순히 정의되지 않은 유형이었다는 않을 것 delete문은 이미 실패?
— Kerrek SB

1
Mohit Jain이 답을 얻은 것 같습니다.
— Peter Hull

1
−1 "로컬 범위 열거 형을 설정하면 런타임에 명령문이 무해합니다." 어, 무의미 해요. 죄송 해요.
— 건배와 hth. - 알프

1
@SteveJessop 감사합니다. 내가 놓친 부분은 불완전한 유형을 삭제하는 것이 UB라는 것입니다.
— Peter Hull

28

를 이해하려면 enum먼저 소멸자가없는 소멸자를 고려하십시오.

~scoped_ptr() {
    delete ptr_;
}

여기서 ptr_A는 C*. 유형의 경우 C이 시점에서 불완전 즉,이 컴파일러가 알고있는 모든 것을 struct C;다음, (1) 기본으로 생성 된 소멸자는 C 인스턴스에 사용 할 - 아무것도에 지적 없습니다. 스마트 포인터에 의해 관리되는 개체에 대해 옳은 일이 아닐 수 있습니다.

불완전한 유형에 대한 포인터를 통한 삭제가 항상 정의되지 않은 동작을 가지고 있었다면 표준은 컴파일러가이를 진단하고 실패하도록 요구할 수 있습니다. 그러나 실제 소멸자가 사소한 경우에는 잘 정의되어 있습니다. 프로그래머는 가질 수 있지만 컴파일러에는없는 지식입니다. 언어가 이것을 정의하고 허용하는 이유는 저를 넘어서지 만 C ++는 오늘날 모범 사례로 간주되지 않는 많은 사례를 지원합니다.

완전한 유형은 알려진 크기를 가지므로 완전한 유형 인 sizeof(C)경우에만 C알려진 소멸자를 사용하여 컴파일됩니다 . 따라서 가드로 사용할 수 있습니다. 한 가지 방법은 간단하게

(void) sizeof(C);  // Type must be complete

나는 것 같아 몇 가지 컴파일러 옵션 컴파일러는 그것을 컴파일되지 것을 알 수 있기 전에, 그리고 그 그것을 멀리 최적화와 함께 그 enum같은 부적합 컴파일러 동작을 방지하는 방법입니다 :

enum { type_must_be_complete = sizeof(C) };

enum단순히 버려지는 표현보다는 선택에 대한 대안적인 설명 은 단순히 개인적인 선호입니다.

또는 James T. Hugget 이이 답변 에 대한 주석에서 제안했듯이 "열거 형은 컴파일 타임에 의사 이식 가능한 오류 메시지를 만드는 방법 일 수 있습니다".


(1) 불완전한 유형에 대해 기본 생성 된 do-nothing 소멸자는 old std::auto_ptr. 너무 교활 해서 국제 C ++ 표준화위원회 Herb Sutter의 위원장이 작성한 PIMPL 관용구에 대한 GOTW 항목에 포함 되었습니다. 물론 현재 std::auto_ptr는 더 이상 사용되지 않으며 대신 다른 메커니즘을 사용합니다.


4
열거 형은 컴파일 타임에 의사 이식 가능한 오류 메시지를 만드는 방법 일 수 있습니다.
— Brice M. Dempsey

1
이 답변은 코드의 동기를 매우 잘 설명한다고 생각하지만 (1) 일부 컴파일러는 sizeof(T)컴파일에 실패하는 대신 불완전한 유형에 대해 0으로 평가 된다는 것을 추가하고 싶습니다 . 그러나 이것은 부적합한 동작입니다. (2) C ++ 11 이후로 사용 static_assert((sizeof(T) > 0), "T must be a complete type");은 우수한 (그리고 관용적 인) 솔루션이 될 것입니다.
— 5gon12eder 2015

@ 5gon12eder; " sizeof(T)불완전한 유형에 대해 0으로 평가 "가있는 C ++ 컴파일러의 예를 제공하십시오 .
— 건배와 hth. - 알프

1
나는 그런 컴파일러를 사용한 적이 없으며 그들이 지금까지 죽었다는 소식을 듣고 놀라지 않을 것입니다. 그렇지 않더라도 부적합한 구현에 대해 신경 쓰지 않는 것은 유효한 결정입니다. 그럼에도 불구하고 libstdc ++ 와 libc ++ 는 모두 유형이 완전한지 확인하기 위해 static_assert(sizeof(T) > 0, "…");각각의 구현에서 사용합니다 std::unique_ptr…
— 5gon12eder

1
… 그래서 이것이 관용적이라고 말하는 것이 안전하다고 생각합니다. 어쨌든 sizeof(T)부울 컨텍스트에서 평가 하는 것은 testing sizeof(T) > 0과 정확히 동일 하므로 미적 이유를 제외하고는 실제로 중요하지 않습니다.
— 5gon12eder 2015

3

확실한 트릭 C이 정의 되어있을 수도 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.