객체의 유형이 C ++의 특정 하위 클래스인지 어떻게 확인합니까?


82

나는 사용 라인을 따라 생각 typeid()했지만 그 유형이 다른 클래스의 하위 클래스인지 묻는 방법을 모르겠습니다 (그런데 추상적입니다)


원하는대로 작동하지 않기 때문에 C ++ 에서 컴파일 타임 에 개체의 유형이 특정 하위 클래스인지 확인하는 방법이 있는지 궁금합니다 std::is_base_of. : 3
KaiserKatze

답변:


38

당신은 정말로해서는 안됩니다. 프로그램이 객체의 클래스를 알아야하는 경우 일반적으로 디자인 결함을 나타냅니다. 가상 기능을 사용하여 원하는 동작을 얻을 수 있는지 확인하십시오. 또한 수행하려는 작업에 대한 자세한 정보가 도움이 될 것입니다.

나는 당신이 다음과 같은 상황에 있다고 가정합니다.

class Base;
class A : public Base {...};
class B : public Base {...};

void foo(Base *p)
{
  if(/* p is A */) /* do X */
  else /* do Y */
}

이것이 당신이 가진 것이라면 다음과 같이 시도하십시오.

class Base
{
  virtual void bar() = 0;
};

class A : public Base
{
  void bar() {/* do X */}
};

class B : public Base
{
  void bar() {/* do Y */}
};

void foo(Base *p)
{
  p->bar();
}

편집 : 이 답변에 대한 논쟁이 수년이 지난 후에도 계속되고 있기 때문에 몇 가지 참고 자료를 던져야한다고 생각했습니다. 기본 클래스에 대한 포인터 또는 참조가 있고 코드에서 개체의 파생 클래스를 알아야하는 경우 Liskov 대체 원칙을 위반하는 입니다. 밥 삼촌 은 이것을 " 객체 지향 디자인에 대한 혐오 "라고 부릅니다 .


20
+1. 이 이름의 정확한 이름은 "말하지 마세요"라고 생각합니다. 기본적으로, 어떤 유형의 객체를 다루고 있는지 물어 보는 case / if 문보다 항상 다형성 (객체에 무엇을해야하는지, 구현이 처리하도록 함)을 선호합니다.
LeopardSkinPillBoxHat

63
네-이게 다 좋습니다-하지만 그 남자는 타입을 해결하는 방법에 대해 알고 싶어했습니다
JohnIdol

7
@Dima, 그리고 누군가가 단지 학습 목적으로 구문을 알고 싶다면 (자바로 작성된 책을 읽고 디자인 결함에 대해 C ++로 번역해야한다고 가정 해 봅시다)?
패치 워크

8
@Dima 슈퍼 클래스를 정의하는 외부 라이브러리로 작업 한 적이 있습니까? 거기에 답을 적용하십시오.
토마스 Zato - 분석 재개 모니카

13
이 대답은 캐스트해야하는 유형을 제어하고 다시 작성할 수 있다는 다소 가정을 만듭니다 . 예를 들어, 다른 GUI 라이브러리를 기반으로 한 GUI 라이브러리에 기능을 추가하고 있는지 여부를 알아야합니다. 위젯의 부모는 스크롤 가능합니다. 원래 라이브러리는이를 테스트 할 방법을 제공하지 않으므로 내 위젯 부모를 스크롤 가능한 위젯의 기본 클래스로 캐스팅해야하는데 정말 짜증납니다. 어쨌든 요점은 당신이 당면한 질문에 대한 실제 대답을 생략했다는 것 입니다.
AnorZaken

125

 

class Base
{
  public: virtual ~Base() {}
};

class D1: public Base {};

class D2: public Base {};

int main(int argc,char* argv[]);
{
  D1   d1;
  D2   d2;

  Base*  x = (argc > 2)?&d1:&d2;

  if (dynamic_cast<D2*>(x) == nullptr)
  {
    std::cout << "NOT A D2" << std::endl;
  }
  if (dynamic_cast<D1*>(x) == nullptr)
  {
    std::cout << "NOT A D1" << std::endl;
  }
}

1
정말 dynamic_cast<>여기 가 필요 합니까? a는하지 않을까요 static_cast<>충분?
krlmlr

15
@krlmlr. x컴파일 시간에 유형을 알 수 있습니까 ? 그렇다면 static_cast<>()작동합니다. x런타임까지 유형을 알 수 없으면 다음이 필요합니다dynamic_cast<>()
마틴 뉴욕

감사. 저는 주로 CRTP에서 다운 캐스트를 사용하고 있습니다. 다른 사용 사례를 계속 잊어 버립니다 ;-)
krlmlr

좋은 대답이지만 여기서 주목할 사항이 있습니다. 삼항 조건부 연산자는 두 번째 및 세 번째 피연산자가 동일한 유형을 가져야합니다. 따라서 이것이 어떻게 이런 식으로 누구에게나 작동하는지 확인하고 대신 if / else를 사용하십시오. 이것은 과거에 효과가 있었을까요? 아무리 해도.
Nikos

@Nikos, 작동 이유는 다음과 같습니다. 1. C ++는 삼항의 경우가 동일한 유형일 필요가 없습니다. 2. 이들은 파생 클래스 포인터 유형이고 파생 클래스 포인터 암시 적 캐스트를베이스로 캐스트합니다.
hazer_hazer

30

dynamic_cast(적어도 다형성 유형의 경우) 와 함께 할 수 있습니다 .

사실, 다시 생각해 보면, 특정 유형인지 알 수 없습니다. dynamic_cast 해당 유형인지 하위 클래스인지는 알 수 있습니다.

template <class DstType, class SrcType>
bool IsType(const SrcType* src)
{
  return dynamic_cast<const DstType*>(src) != nullptr;
}

서브 클래스 가 다형성 유형이 아닌 경우 는 언제 입니까?
OJFord

6
@OllieFord : 가상 기능이 없을 때.
Drew Hall

다른 말했다,시는 std::is_polymorphic_v<T>것입니다 false.
Xeverous

7

아래 코드는이를 수행하는 세 가지 방법을 보여줍니다.

  • 가상 기능
  • typeid
  • dynamic_cast
#include <iostream>
#include <typeinfo>
#include <typeindex>

enum class Type {Base, A, B};

class Base {
public:
    virtual ~Base() = default;
    virtual Type type() const {
        return Type::Base;
    }
};

class A : public Base {
    Type type() const override {
        return Type::A;
    }
};

class B : public Base {
    Type type() const override {
        return Type::B;
    }
};

int main()
{
    const char *typemsg;
    A a;
    B b;
    Base *base = &a;             // = &b;    !!!!!!!!!!!!!!!!!
    Base &bbb = *base;

    // below you can replace    base    with  &bbb    and get the same results

    // USING virtual function
    // ======================
    // classes need to be in your control
    switch(base->type()) {
    case Type::A:
        typemsg = "type A";
        break;
    case Type::B:
        typemsg = "type B";
        break;
    default:
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING typeid
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    std::type_index ti(typeid(*base));
    if (ti == std::type_index(typeid(A))) {
        typemsg = "type A";
    } else if (ti == std::type_index(typeid(B))) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING dynamic_cast
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    if (dynamic_cast</*const*/ A*>(base)) {
        typemsg = "type A";
    } else if (dynamic_cast</*const*/ B*>(base)) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;
}

위의 프로그램은 다음을 인쇄합니다.

type A
type A
type A

6

dynamic_cast유형이 상속 계층 구조의 어디에서나 대상 유형을 포함하는지 확인할 수 있습니다 (예, and 에서 B상속하면를 으로 직접 변환 할 수있는 잘 알려지지 않은 기능입니다 ). 개체의 정확한 유형을 결정할 수 있습니다. 그러나 둘 다 극도로 드물게 사용해야합니다. 이미 언급했듯이 동적 유형 식별은 디자인 결함을 나타 내기 때문에 항상 피해야합니다. (당신이 알고있는 경우 또한, 객체는 대상 유형의 확실히, 당신이 가진 내리 뜬을 할 수있다 . 이벤트 부스트 와 풀이 죽은을 할 것입니다 및 디버그 모드로, 그리고 릴리스 모드에서 단지를 사용합니다 ).ACA*C*typeid()static_castpolymorphic_downcastdynamic_castassertstatic_cast


4

C ++에서 개체의 유형을 절대 확인하고 싶지 않다는 데 동의하지 않습니다. 피할 수 있다면 그렇게해야한다는 데 동의합니다. 어떤 상황에서도 절대 이렇게하지 말라고 말하는 것은 너무 멀리 가고 있습니다. 이것은 매우 많은 언어로 할 수 있으며 삶을 훨씬 더 쉽게 만들 수 있습니다. 예를 들어 Howard Pinsley는 C #에 대한 그의 게시물에서 방법을 보여주었습니다.

저는 Qt 프레임 워크로 많은 작업을합니다. 일반적으로 나는 그들이 일하는 방식에 따라 내가하는 일을 모델링한다 (적어도 그들의 프레임 워크에서 작업 할 때). QObject 클래스는 모든 Qt 객체의 기본 클래스입니다. 이 클래스에는 빠른 하위 클래스 검사로 isWidgetType () 및 isWindowType () 함수가 있습니다. 그렇다면 본질적으로 비교 가능한 자신의 파생 클래스를 확인할 수없는 이유는 무엇입니까? 다음은 이러한 다른 게시물 중 일부의 QObject 스핀 오프입니다.

class MyQObject : public QObject
{
public:
    MyQObject( QObject *parent = 0 ) : QObject( parent ){}
    ~MyQObject(){}

    static bool isThisType( const QObject *qObj )
    { return ( dynamic_cast<const MyQObject*>(qObj) != NULL ); }
};

그런 다음 QObject에 대한 포인터를 전달할 때 정적 멤버 함수를 호출하여 파생 클래스를 가리키는 지 확인할 수 있습니다.

if( MyQObject::isThisType( qObjPtr ) ) qDebug() << "This is a MyQObject!";

4

문제를 올바르게 이해했는지 모르겠습니다. 제 말로 다시 말씀 드리겠습니다.

문제 : 주어진 클래스 BD, D의 하위 클래스 인지 확인 B(또는 그 반대?)

솔루션 : 템플릿 마법을 사용하십시오! 좋아, 진지하게 당신은 전설적인 C ++ 저자 인 Andrei Alexandrescu가 만든 훌륭한 템플릿 메타 프로그래밍 라이브러리 인 LOKI를 살펴볼 필요가 있습니다.

보다 구체적으로 LOKI를 다운로드 하고 TypeManip.h소스 코드에 헤더 를 포함 시킨 후 SuperSubclass다음과 같이 클래스 템플릿을 사용합니다.

if(SuperSubClass<B,D>::value)
{
...
}

문서에 SuperSubClass<B,D>::value따르면가 B의 공용 기반 D이거나 BD동일한 유형의 별칭 이면 true 입니다.

즉 하나 D의 하위 클래스 B또는 D동일하다 B.

이게 도움이 되길 바란다.

편집하다:

의 평가 SuperSubClass<B,D>::value는를 사용하는 일부 메서드와 달리 컴파일 타임 에 발생 dynamic_cast하므로 런타임에이 시스템을 사용하는 데 따른 불이익이 없습니다.


3
#include <stdio.h>
#include <iostream.h>

class Base
{
  public: virtual ~Base() {}

  template<typename T>
  bool isA() {
    return (dynamic_cast<T*>(this) != NULL);
  }
};

class D1: public Base {};
class D2: public Base {};
class D22: public D2 {};

int main(int argc,char* argv[]);
{
  D1*   d1  = new D1();
  D2*   d2  = new D2();
  D22*  d22 = new D22();

  Base*  x = d22;

  if( x->isA<D22>() )
  {
    std::cout << "IS A D22" << std::endl;
  }
  if( x->isA<D2>() )
  {
    std::cout << "IS A D2" << std::endl;
  }
  if( x->isA<D1>() )
  {
    std::cout << "IS A D1" << std::endl;
  }
  if(x->isA<Base>() )
  {
    std::cout << "IS A Base" << std::endl;
  }
}

결과:

IS A D22
IS A D2
IS A Base

2

나는 사용 라인을 따라 생각하고 있었다 typeid()...

예, 다음을 비교하여 수행 할 수 있습니다 typeid().name().. 이미 설명한 상황을 취하면 다음과 같습니다.

class Base;
class A : public Base {...};
class B : public Base {...};

void foo(Base *p)
{
  if(/* p is A */) /* do X */
  else /* do Y */
}

가능한 구현 foo(Base *p)은 다음과 같습니다.

#include <typeinfo>

void foo(Base *p)
{
    if(typeid(*p) == typeid(A))
    {
        // the pointer is pointing to the derived class A
    }  
    else if (typeid(*p).name() == typeid(B).name()) 
    {
        // the pointer is pointing to the derived class B
    }
}

1
typeid (). name ()과 typeid ()의 조합을 혼합하는 이유는 무엇입니까? 왜 항상 typeid ()를 비교하지 않습니까?
Silicomancer

1

RTTI를 사용하지 않는 한 템플릿을 사용하여 컴파일 타임에만 수행 할 수 있습니다.

유형에 대한 정보를 포함하는 type_info 구조에 대한 포인터를 생성하는 typeid 함수를 사용할 수 있습니다.

Wikipedia 에서 읽기


이 맥락에서 RTTI를 언급하는 데 투표했으며 다른 사람들은 모두 무시했습니다.
ManuelSchneid3r

1

C #에서는 간단히 다음과 같이 말할 수 있습니다.

if (myObj is Car) {

}

8
나는 포스터가 그의 질문을 편집하고 그의 언어 선택을 표시하기 전에 대답했습니다.
Howard Pinsley

1
나는 찬성하고 있으며 OP가 자신의 요청을 지정한 것은 대답의 잘못이 아닙니다.
Tomáš Zato-Monica 복원

0

템플릿 (또는 SFINAE (Substitution Failure Is Not An Error))을 사용 하여 수행 할 수 있습니다 . 예:

#include <iostream>

class base
{
public:
    virtual ~base() = default;
};

template <
    class type,
    class = decltype(
        static_cast<base*>(static_cast<type*>(0))
    )
>
bool check(type)
{
    return true;
}

bool check(...)
{
    return false;
}

class child : public base
{
public:
    virtual ~child() = default;
};

class grandchild : public child {};

int main()
{
    std::cout << std::boolalpha;

    std::cout << "base:       " << check(base())       << '\n';
    std::cout << "child:      " << check(child())      << '\n';
    std::cout << "grandchild: " << check(grandchild()) << '\n';
    std::cout << "int:        " << check(int())        << '\n';

    std::cout << std::flush;
}

산출:

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