program tip

`typeid` 코드에서`? :`의 이상한 사용

radiobox 2020. 12. 13. 09:10
반응형

`typeid` 코드에서`? :`의 이상한 사용


작업중인 프로젝트 중 하나에서이 코드가 표시됩니다.

struct Base {
  virtual ~Base() { }
};

struct ClassX {
  bool isHoldingDerivedObj() const {
    return typeid(1 ? *m_basePtr : *m_basePtr) == typeid(Derived);
  }
  Base *m_basePtr;
};

나는 그런 typeid사용을 본 적이 없습니다 . ?:그냥하는 대신 이상한 춤을 추는 typeid(*m_basePtr)거죠? 이유가 있을까요? Base다형성 클래스 (가상 소멸자 포함)입니다.

편집 :이 코드의 다른 곳에서 나는 이것을보고 있으며 동등하게 "불필요한"것으로 보입니다.

template<typename T> T &nonnull(T &t) { return t; }

struct ClassY {
  bool isHoldingDerivedObj() const {
    return typeid(nonnull(*m_basePtr)) == typeid(Derived);
  }
  Base *m_basePtr;
};

나는 그것이 최적화 라고 생각 합니다 ! 약간 알려지고 드물게 사용되는 기능은 typeid인수의 null 역 참조가 typeid일반적인 UB 대신 예외 던진다는 것입니다.

뭐? 진심이야? 취 했어?

과연. 예. 아니.

int * p = 0;
*피; // UB
typeid (* p); // 던짐

예, 이것은 언어 추악함의 C ++ 표준에 의해서도 추악합니다.

OTOH, 이것은 의 인수 에서 작동하지 않으므로 typeid혼란을 추가하면이 "기능"이 취소됩니다.

int * p = 0;
typeid (1? * p : * p); // UB
typeid (identity (* p)); // UB

기록을 위해 : 나는이 메시지에서 역 참조를하기 전에 포인터가 null이 아닌지 컴파일러가 자동으로 확인하는 것이 반드시 미친 일이라고 주장하지 않습니다. 나는 단지이 검사를 수행하는 것은 역 참조가의 즉각적인 인수 될 때 말하고 typeid, 아닌 다른 곳에서 완전히 미쳤다. (아마도 일부 초안에 삽입 된 장난 일 수 있으며 제거되지 않았습니다.)

레코드 : 필자는 이전 "For the record"에서 포인터가 null이 아닌 자동 검사를 삽입하고 null이 역 참조 될 때 예외 (Java에서와 같이)를 발생시키는 것이 합리적이라고 주장하지 않습니다. : 일반적으로 null 역 참조에서 예외를 던지는 것은 터무니 없습니다. 이것은 프로그래밍 오류이므로 예외가 도움이되지 않습니다. 어설 션 실패가 필요합니다.


내가 볼 수있는 유일한 효과 lvalue 인 일반 대신 rvalue1 ? X : X제공 한다는 입니다. 이것은 배열 (포인터로 붕괴)과 같은 것들에 중요 할 수 있지만 클래스로 알려진 경우 중요하지 않다고 생각합니다 . 아마도 rvalue-ness 중요한 곳에서 복사 된 것일까 요? 그것은 "화물 컬트 프로그래밍"에 대한 의견을 뒷받침합니다.XXtypeid()Derived

아래의 주석과 관련하여 나는 테스트를했고 충분히 확신 typeid(array) == typeid(1 ? array : array)했기 때문에 어떤 의미에서 나는 틀렸지 만 내 오해는 여전히 원래 코드로 이어지는 오해와 일치 할 수 있습니다!


이 동작은 [expr.typeid] / 2 (N3936)에서 다룹니다.

typeid유형이 다형성 클래스 유형 인 glvalue 표현식에가 적용될 때 결과 는 glvalue가 참조 std::type_info하는 가장 많이 파생 된 객체 (즉, 동적 유형)의 유형을 나타내는 객체를 참조합니다. *포인터에 단항 연산자를 적용하여 glvalue 표현식을 얻고 포인터가 널 포인터 값이면 typeid표현식은 예외 유형의 핸들러와 일치하는 유형의 예외를 던집니다 std::bad_typeid.

표현식 1 ? *p : *p은 항상 lvalue입니다. 이것은 *plvalue이고 [expr.cond] / 4는 삼항 연산자에 대한 두 번째 및 세 번째 피연산자가 동일한 유형 및 값 범주를 갖는 경우 연산자의 결과에도 해당 유형 및 값 범주가 있음을 나타냅니다.

따라서 1 ? *m_basePtr : *m_basePtr이다 좌변 유형은 Base. 이후 Base가상 소멸자를 가지고, 그것은 다형성 클래스 유형입니다.

따라서이 코드는 실제로 " typeid형이 다형성 클래스 유형 인 glvalue 표현식에 언제 적용되는지 "의 예입니다 .


이제 위 인용문의 나머지 부분을 읽을 수 있습니다. glvalue 표현식은 " 포인터에 단항 연산자를 적용하여 얻지 " 않았습니다* . 삼항 연산자를 통해 얻었습니다. 따라서 표준 m_basePtr은 null 인 경우 예외가 발생하도록 요구하지 않습니다 .

The behaviour in the case that m_basePtr is null would be covered by the more general rules about dereferencing a null pointer (which are a bit murky in C++ actually but for practical purposes we'll assume that it causes undefined behaviour here).


Finally: why would someone write this? I think curiousguy's answer is the most plausible suggestion so far: with this construct, the compiler does not have to insert a null pointer test and code to generate an exception, so it is a micro-optimization.

Presumably the programmer is either happy enough that this will never be called with a null pointer, or happy to rely on a particular implementation's handling of null pointer dereference.


I suspect some compiler was, for the simple case of

typeid(*m_basePtr)

returning typeid(Base) always, regardless of the runtime type. But turning it to an expression/temporary/rvalue made the compiler give the RTTI.

Question is which compiler, when, etc. I think GCC had problems with typeid early on, but it is a vague memory.

참고URL : https://stackoverflow.com/questions/6795890/weird-use-of-in-typeid-code

반응형