소멸자의 이상한 열거 형


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.