Tipo de identificação única classe que é segura e segura através de limites de biblioteca

StackOverflow https://stackoverflow.com/questions/922442

  •  06-09-2019
  •  | 
  •  

Pergunta

Eu gostaria de receber qualquer ajuda, como C ++ não é minha língua primária.

Eu tenho uma classe de modelo que é derivado em várias bibliotecas. Eu estou tentando descobrir uma maneira de atribuir exclusivamente um int id para cada classe derivada. Eu preciso ser capaz de fazê-lo a partir de um método estático, porém, ie.


template < class DERIVED >
class Foo
{
public:
    static int s_id()
    {
        // return id unique for DERIVED
    }
    // ...
};
Obrigado!

Foi útil?

Solução 4

Aqui está o que eu acabei fazendo. Se você tiver qualquer feedback (prós, contras) por favor me avise.


template < class DERIVED >
class Foo
{
public:
    static const char* name(); // Derived classes will implement, simply
// returning their class name static int s_id() { static const int id = Id_factory::get_instance()->get_id(name()); return id; } // ... };

Essencialmente, o ID será atribuído depois de fazer uma comparação de string em vez de uma comparação ponteiro. Este não é o ideal em termos de velocidade, mas eu fiz a const ID estática por isso só terá de calcular uma vez para cada derivada.

Outras dicas

Isto pode ser feito com muito pouco código:

template < class DERIVED >
class Foo
{
public:
    static int s_id()
    {
        return reinterpret_cast<int>(&s_id);
    }
};

Na moderna C ++ (03 - supondo que você está usando um compilador recente como gcc) você pode usar o typeid palavra-chave para obter um objeto type_info que fornece informações tipo básico, pelo menos em tempo de execução - que é uma característica padrão (e, em seguida, cross-platform).

Eu levei o exemplo da wikipedia e acrescentou um cheque template / herança, parece que funciona bem, mas eu não estou certo para a versão int (que é um hack explora a hipótese de que o compilador terá os nomes de tipos em algum lugar a ler apenas o espaço de memória ... que poderia ser uma suposição errada).

O identificador de cadeia parece muito melhor para a identificação de plataforma cruzada, se você pode usá-lo em você caso. Não é cross-compilador compatível como o nome dá-lhe é "implementação definido" pelo padrão -. Como sugerido nos comentários

O código de aplicação de teste completo:

#include <iostream>
#include <typeinfo>  //for 'typeid' to work

class Person 
{
public:
   // ... Person members ...
   virtual ~Person() {}
};

class Employee : public Person 
{
   // ... Employee members ...
};

template< typename DERIVED >
class Test
{
public:
    static int s_id()
    {
        // return id unique for DERIVED
        // NOT SURE IT WILL BE REALLY UNIQUE FOR EACH CLASS!!
        static const int id = reinterpret_cast<int>(typeid( DERIVED ).name());
        return id;
    }

    static const char* s_name()
    {
        // return id unique for DERIVED
        // ALWAYS VALID BUT STRING, NOT INT - BUT VALID AND CROSS-PLATFORM/CROSS-VERSION COMPATBLE
        // AS FAR AS YOU KEEP THE CLASS NAME
        return typeid( DERIVED ).name();
    }
};

int wmain () 
{
    Person person;
    Employee employee;
    Person *ptr = &employee;



    std::cout << typeid(person).name() << std::endl;   // Person (statically known at compile-time)
    std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-time)
    std::cout << typeid(ptr).name() << std::endl;      // Person * (statically known at compile-time)
    std::cout << typeid(*ptr).name() << std::endl;     // Employee (looked up dynamically at run-time
                                                    // because it is the dereference of a pointer to a polymorphic class)

    Test<int> test;
    std::cout << typeid(test).name() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_name() << std::endl;    

    Test< Person > test_person;
    std::cout << test_person.s_name() << std::endl;    
    std::cout << test_person.s_id() << std::endl;    

    Test< Employee > test_employee;
    std::cout << test_employee.s_name() << std::endl;    
    std::cout << test_employee.s_id() << std::endl;    

    Test< float > test_float;
    std::cout << test_float.s_name() << std::endl;    
    std::cout << test_float.s_id() << std::endl;    


    std::cin.ignore();
    return 0;
}

Saídas:

class Person
class Employee
class Person *
class Employee
class Test<int>
3462688
3462688
3462688
int
class Person
3421584
class Employee
3462504
float
3462872

Isso funciona, pelo menos em VC10Beta1 e VC9, deve trabalhar em GCC. By the way, usar typeid (e dynamic_cast) você tem que permitir que informações sobre tipo de tempo de execução do seu compilador. Deve ser ativada por padrão. Em alguns plateform / compilador (estou pensando em alguns hardwares embarcados) RTTI não está ligado, porque tem um custo, por isso, em alguns casos extremos você vai ter que encontrar uma solução melhor.

Na minha empresa anterior que fizemos isso criando uma macro que levaria o nome da classe como um parâmetro, criar um local estático com a identificação única (com base no nome da classe) e, em seguida, criar uma substituição de uma função virtual declarado na classe base que devolveu o membro estático. Dessa forma, você pode obter o ID em tempo de execução de qualquer instância da hierarquia de objetos, semelhante ao 'getClass ()' método em um objeto de java, embora muito mais primitivo.

Não há nada padronizado. Além disso, não há nenhum truque que eu descobri que o infalível.

Melhor eu tenho sido capaz de chegar a:

template < class DERIVED, int sid >
class Foo
{
    public:    
      static int s_id()    
      {        
          return sid;
      }    
};

Foo<MyClass, 123456>   derivedObject;

Que tipo de ID? Você está procurando um int atomicamente aumentando? Se uma string é bom, o que acontece com:

static string s_id()
{
   return typeid(Foo<DERIVED>).name();
}

Se ele precisa ser um int, mas não aumentando automaticamente, você poderia hash que para um 128-bit inteiro susceptíveis de ter colisões (embora provavelmente um número maior do que você precisa)

Eu não estou 100% feliz com as respostas até agora e eu tenho trabalhado para fora uma solução para mim. A idéia é calcular um hash do nome do tipo usando typeinfo. Tudo é feito uma vez ao carregar o aplicativo de modo que não há sobrecarga de tempo de execução. Esta solução vai funcionar também usando bibliotecas compartilhadas como o nome do tipo e o hash será consistente.

Este é o uso de código I. Isso funciona muito bem para mim.

#include <string>
#include <typeinfo>
#include <stdint.h>

//###########################################################################
// Hash
//###########################################################################
#if __SIZEOF_POINTER__==8
inline uint64_t hash(const char *data, uint64_t len) {
    uint64_t result = 14695981039346656037ul;
    for (uint64_t index = 0; index < len; ++index)
    {
        result ^= (uint64_t)data[index];
        result *= 1099511628211ul;
    }
    return result;
}
#else
inline uint32_t hash(const char *data, uint32_t len) {
    uint32_t result = 2166136261u;
    for (uint32_t index = 0; index < len; ++index)
    {
        result ^= (uint32_t)data[index];
        result *= 16777619u;
    }
    return result;
}
#endif
inline size_t hash(const std::string & str) { return hash(str.c_str(), str.length()); }

//###########################################################################
// TypeId
//###########################################################################
typedef size_t TypeId;

template<typename T>
static const std::string & typeName() {
    static const std::string tName( typeid(T).name() );
    return tName;
}
template<typename T>
static TypeId typeId() {
    static const TypeId tId = hash( typeName<T>() );
    return tId;
}

O abaixo trecho funciona no VS (2015) e compilações Release:

template <typename T>
struct TypeId
{
  static size_t Get()
  {
    return reinterpret_cast<size_t>(&sDummy);
  }

private:
  static char sDummy;
};

template <typename T>
char TypeId<T>::sDummy; // don't care about value

Além disso experimentado e testado em GCC v7.3 (Ubuntu 16.04) e v10.0.0 LLVM (Mac OS High Sierra).

exercício para o leitor:., Pelo menos, const e tipos ref deve obter o mesmo tipo ID como o tipo de matéria-

Você pode fazer o seguinte:

#include <iostream>


template <int id = 5>
class blah
{
public:
    static const int cid = id; 
};

int main(int argc, char *argv[])
{
    std::cout << blah<>::cid << " " << blah<10>::cid << std::endl;

}

Eu não sei se é uma boa idéia embora. A parte blah<> é um pouco unintuitive também. Talvez você é melhor para atribuir-lhes ids manualmente, ou você pode criar um tipo de base, dar-lhe um argumento modelo com o ID de classe.

#include <stdint.h>
#include <stdio.h>

#define DEFINE_CLASS(class_name) \
    class class_name { \
    public: \
        virtual uint32_t getID() { return hash(#class_name); } \

// djb2 hashing algorithm
uint32_t hash(const char *str)
{
    unsigned long hash = 5381;
    int c;

    while ((c = *str++))
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

DEFINE_CLASS(parentClass)

    parentClass() {};
    ~parentClass() {};
};

DEFINE_CLASS(derivedClass : public parentClass)

    derivedClass() : parentClass() {};
    ~derivedClass() {};
};

int main() {
    parentClass parent;
    derivedClass derived;
    printf("parent id: %x\nderived id: %x\n", parent.getID(), derived.getID());
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top