Localizando o tipo de um objeto em C ++

147

Eu tenho uma classe A e outra classe que herda dela, B. Estou substituindo uma função que aceita um objeto do tipo A como parâmetro, portanto, tenho que aceitar um A. No entanto, mais tarde chamo funções que somente B tem, então eu quero retornar false e não continuar se o objeto passado não for do tipo B.

Qual é a melhor maneira de descobrir que tipo de objeto passou para minha função?

Lemnisca
fonte

Respostas:

162

dynamic_cast deve fazer o truque

TYPE& dynamic_cast<TYPE&> (object);
TYPE* dynamic_cast<TYPE*> (object);

A dynamic_castpalavra-chave lança um dado de um ponteiro ou tipo de referência para outro, executando uma verificação de tempo de execução para garantir a validade da conversão.

Se você tentar converter o ponteiro para um tipo que não é um tipo de objeto real, o resultado da conversão será NULL. Se você tentar converter para fazer referência a um tipo que não é um tipo de objeto real, o elenco emitirá uma bad_castexceção.

Verifique se há pelo menos uma função virtual na classe Base para fazer o dynamic_cast funcionar.

Tópico da Wikipedia Informações sobre o tipo de tempo de execução

O RTTI está disponível apenas para classes polimórficas, o que significa que elas têm pelo menos um método virtual. Na prática, isso não é uma limitação porque as classes base devem ter um destruidor virtual para permitir que objetos de classes derivadas executem a limpeza adequada se forem excluídos de um ponteiro base.

yesraaj
fonte
1
O que você quer dizer com deve haver uma função virtual na classe Base para fazer o dynamic_cast funcionar. Isso me parece importante, que vou adivinhar.
GiCo 19/08/2015
3
A OK encontrou: Informações sobre o tipo de tempo de execução (RTTI) estão disponíveis apenas para classes polimórficas, o que significa que elas têm pelo menos um método virtual. dynamic_cast e typeid precisam de RTTI.
GiCo 19/08/2015
Não dynamic_castjoga se não for conversível? Existe uma maneira de fazer isso sem gerar um arremesso?
jww 31/08/16
A* aptr = dynamic_cast<A*>(ptr);// não deveria ser assim
Mehdi Karamosly 4/16
Isso funciona para PODs que foram convertidos em a uint8_t*? Ou seja, posso verificar isso uint32_t* x = dynamic_cast<uint32_t*>(p), onde pestá uint8_t*? (Estou tentando encontrar um teste para punir violações).
JWW
157

O elenco dinâmico é o melhor para sua descrição do problema, mas quero apenas acrescentar que você pode encontrar o tipo de classe com:

#include <typeinfo>

...
string s = typeid(YourClass).name()
Robocide
fonte
4
Bom se você realmente não sabe qual é o seu objeto. A resposta aceita assume que você faz.
unludo
4
@xus Sim. que é parte de colectores std
Amey Jah
8
Eu não vejo como. Os nomes de identificação de tipo não precisam ser úteis e estão definidos para implementação.
Shoe
3
Mais interessante aqui: os nomes de instâncias da mesma classe não precisam ser iguais. Entretanto, o typeid tem de comparar igual para instâncias da mesma classe, consulte stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo
1
Nota gcc retorna o nome emaranhado, por exemplo 11MyClass. Para desmontar, você pode usar a biblioteca de extensão ABI em cxxabi.h. Isto dá-lhe abi::__cxa_demangleo que lhe dará o verdadeiro nome
David G
27

Isso se chama RTTI , mas você quase certamente deseja reconsiderar seu design aqui, porque encontrar o tipo e fazer coisas especiais com base nele torna seu código mais frágil.

Ana Betts
fonte
4
Verdade. Infelizmente eu estou trabalhando em um projeto existente, então não posso realmente ir mudando o design, ou qualquer coisa na classe A.
lemnisca
11

Apenas para concluir, construirei o Robocide e apontarei que typeidpode ser usado sozinho, sem usar o nome ():

#include <typeinfo>
#include <iostream>

using namespace std;

class A {
public:
    virtual ~A() = default; // We're not polymorphic unless we
                            // have a virtual function.
};
class B : public A { } ;
class C : public A { } ;

int
main(int argc, char* argv[])
{
    B b;
    A& a = b;

    cout << "a is B: " << boolalpha << (typeid(a) == typeid(B)) << endl;
    cout << "a is C: " << boolalpha << (typeid(a) == typeid(C)) << endl;
    cout << "b is B: " << boolalpha << (typeid(b) == typeid(B)) << endl;
    cout << "b is A: " << boolalpha << (typeid(b) == typeid(A)) << endl;
    cout << "b is C: " << boolalpha << (typeid(b) == typeid(C)) << endl;
}

Resultado:

a is B: true
a is C: false
b is B: true
b is A: false
b is C: false
firebush
fonte
9

Provavelmente, incorpore aos seus objetos uma "tag" de identificação e use-a para distinguir entre objetos da classe A e objetos da classe B.

No entanto, isso mostra uma falha no design. Idealmente, os métodos em B que A não possui devem fazer parte de A, mas são deixados em branco e B os substitui. Isso acaba com o código específico da classe e está mais no espírito do OOP.

espaço livre
fonte
8

Você está procurando dynamic_cast<B*>(pointer)

Joshua
fonte
4

Porque sua classe não é polimórfica. Experimentar:

struct BaseClas { int base; virtual ~BaseClas(){} };
class Derived1 : public BaseClas { int derived1; };

Agora BaseClasé polimórfico. Alterei a classe para struct porque os membros de uma estrutura são públicos por padrão.

c64zottel
fonte
3

Sua descrição é um pouco confusa.

De um modo geral, embora algumas implementações de C ++ possuam mecanismos para isso, você não deve perguntar sobre o tipo. Em vez disso, você deve fazer um dynamic_cast no ponteiro para A. O que isso fará é que, em tempo de execução, o conteúdo real do ponteiro para A será verificado. Se você tiver um B, receberá o ponteiro em B. Caso contrário, receberá uma exceção ou nulo.

Uri
fonte
1
Note-se que você receberá uma exceção apenas se executar uma conversão de referência que falhará. ou seja, dynamic_cast <T &> (t). O ponteiro com falha lança o retorno NULL. dynamic_cast <T *> (t)
AlfaZulu 9/08/08
Sim, eu deveria ter esclarecido isso melhor. Obrigado. Eu gostaria que houvesse uma palavra que descreva nos tipos C que são por referência e não por valor.
Uri
3

Como outros indicaram, você pode usar dynamic_cast. Mas geralmente usar dynamic_cast para descobrir o tipo de classe derivada em que você está trabalhando indica um design incorreto. Se você estiver substituindo uma função que usa o ponteiro de A como parâmetro, ele deverá poder trabalhar com os métodos / dados da própria classe A e não deve depender dos dados da classe B. No seu caso, em vez de substituir, se você tenha certeza de que o método que você está escrevendo funcionará apenas com a classe B, então você deve escrever um novo método na classe B.

Naveen
fonte
1

Use funções sobrecarregadas. Não requer suporte a dynamic_cast ou mesmo RTTI:

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

class Foo {
public:
    void Bar(A& a) {
        // do something
    }
    void Bar(B& b) {
        Bar(static_cast<A&>(b));
        // do B specific stuff
    }
};
jmucchiello
fonte
Direito da pergunta original: "Mais tarde chamo funções que apenas B tem" - como a sobrecarga funcionaria nesse caso?
Marcin Gil
Quando você chama Bar com um A, nada de B acontece. Quando você chama Bar com um B, métodos que só existem em B podem ser chamados. Você leu a pergunta original? Bar é seu "Estou substituindo uma função que aceita um objeto do tipo A como um parâmetro"
jmucchiello
7
Isso não funciona com o polimorfismo dinâmico, que suspeito que o questionador esteja usando. O C ++ não pode selecionar uma sobrecarga com base na classe de tempo de execução do parâmetro, apenas com base no tipo de tempo de compilação.
Steve Jessop
1

Se você pode acessar a biblioteca de aumento, talvez seja a função type_id_with_cvr () que você precisa, que pode fornecer o tipo de dados sem remover os modificadores const, volatile, & e && . Aqui está um exemplo simples no C ++ 11:

#include <iostream>
#include <boost/type_index.hpp>

int a;
int& ff() 
{
    return a;
}

int main() {
    ff() = 10;
    using boost::typeindex::type_id_with_cvr;
    std::cout << type_id_with_cvr<int&>().pretty_name() << std::endl;
    std::cout << type_id_with_cvr<decltype(ff())>().pretty_name() << std::endl;
    std::cout << typeid(ff()).name() << std::endl;
}

Espero que isso seja útil.

Kehe CAI
fonte