2009-07-10 13 views
7

Tenemos un subproyecto 'commonUtils' que tiene muchos fragmentos de código genéricos utilizados en el proyecto principal. Una de estas cosas interesantes que vi fue: -Pruebe si una clase es polimórfica

/********************************************************************* 
If T is polymorphic, the compiler is required to evaluate the typeid 
stuff at runtime, and answer will be true. If T is non-polymorphic, 
the compiler is required to evaluate the typeid stuff at compile time, 
whence answer will remain false 
*********************************************************************/ 
template <class T> 
bool isPolymorphic() { 
    bool answer=false; 
    typeid(answer=true,T()); 
    return answer; 
} 

yo creía que el comentario y pensé que es bastante una plantilla interesante aunque no es utilizado en todo el proyecto. Intenté usarlo así solo por curiosidad ...

class PolyBase { 
public: 
    virtual ~PBase(){} 
}; 

class NPloyBase { 
public: 
    ~NBase(){} 
}; 


if (isPolymorphic<PolyBase>()) 
    std::cout<<"PBase = Polymorphic\n"; 
if (isPolymorphic<NPolyBase>()) 
    std::cout<<"NBase = Also Polymorphic\n"; 

Pero ninguno de esos resultados alguna vez es cierto. MSVC 2005 no da advertencias, pero Comeau advierte que la expresión de tipo no tiene ningún efecto. La sección 5.2.8 en el estándar de C++ no dice nada como lo que dice el comentario, es decir, que typeid se evalúa en tiempo de compilación para tipos no polimórficos y en tiempo de ejecución para tipos polimórficos.

1) Así que supongo que el comentario es engañoso/simplemente incorrecto o dado que el autor de este código es un programador de C++ bastante avanzado, ¿me falta algo?

2) OTOH, me pregunto si podemos probar si una clase es polimórfica (tiene al menos una función virtual) usando alguna técnica?

3) ¿Cuándo querría saber si una clase es polimórfica? Conjetura salvaje; para obtener la dirección de inicio de una clase usando dynamic_cast<void*>(T) (como dynamic_cast funciona solo en clases polimórficas).

A la espera de sus opiniones.

Gracias de antemano,

+0

Er, Si el autor es un programador senior de C++, ¿por qué no consultar con él primero? ... A menudo aprenderás mucho de muchachos experimentados. – stefanB

+9

Bueno, si pudiera, no lo habría preguntado en stackoverflow :-) – Abhay

Respuesta

8

No puedo imaginar cómo cualquier manera posible que typeid se podría utilizar para comprobar que el tipo es polimórfico. Ni siquiera se puede usar para afirmar que lo es, ya que typeid funcionará en cualquier tipo. Boost tiene una implementación here. En cuanto a por qué podría ser necesario, un caso que conozco es la biblioteca Boost.Serialization. Si está guardando un tipo no polimórfico, puede simplemente guardarlo. Si guardas uno polimórfico, debes obtener su tipo dinámico usando typeid, y luego invocar el método de serialización para ese tipo (buscándolo en alguna tabla).

Actualización: parece que estoy realmente equivocado. Considere esta variante:

template <class T> 
bool isPolymorphic() { 
    bool answer=false; 
    T *t = new T(); 
    typeid(answer=true,*t); 
    delete t; 
    return answer; 
} 

Este trabajo realmente hace como su nombre indica, precisamente por comentario en su fragmento de código original. La expresión dentro de typeid no se evalúa si "no designa un valor l de tipo de clase polimórfica" (estándar 3.2/2). Entonces, en el caso anterior, si T no es polimórfico, la expresión de tipo no se evalúa. Si T es polimórfico, entonces * t es de hecho lvalor de tipo polimórfico, por lo que toda la expresión tiene que ser evaluada.

Ahora, su ejemplo original sigue siendo incorrecto :-). Usó T(), no *t. Y T() crear rvalue (estándar 3.10/6). Entonces, todavía produce una expresión que no es "lvalue de clase polimórfica".

Es un truco bastante interesante. Por otro lado, su valor práctico es algo limitado, porque while boost :: is_polymorphic te da una constante en tiempo de compilación, esta te da un valor de tiempo de ejecución, por lo que no puedes instanciar diferentes códigos para tipos polimórficos y no polimórficos .

+0

Sí, conozco la implementación de impulso que usa aproximadamente la técnica de sizeof(). Gracias por el tidbit de serialización. Me interesó saber si esa plantilla commonUtils es correcta en primer lugar y, en segundo lugar, vale la pena conservarla en el proyecto. – Abhay

+2

Ah ha, un truco interesante, pero tu cita del 3.10/6 fue esclarecedora, gracias. Se puede elegir sobre la versión con plantilla cuando el tamaño binario importa o si no se puede confiar en que el usuario proporcione un dtor virtual en una clase polimórfica. ¡Por desgracia, no puedo votar 2 veces! – Abhay

3


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

class NPolyBase { 
public: 
    ~NPolyBase(){} 
}; 

template<class T> 
struct IsPolymorphic 
{ 
    struct Derived : T { 
     virtual ~Derived(); 
    }; 
    enum { value = sizeof(Derived)==sizeof(T) }; 
}; 


void ff() 
{ 
    std::cout << IsPolymorphic<PolyBase >::value << std::endl; 
    std::cout << IsPolymorphic<NPolyBase>::value << std::endl; 
} 

+1

No estoy seguro de si esto es completamente infalible. Un compilador puede agregar relleno entre subobjetos, en cuyo caso el truco sizeof() no funcionaría. – Abhay

+1

Esto se rompería cuando, digamos que Derived define sus propias variables miembro, por lo que no es práctico. – Indy9000

+0

@Indeera: no agregará ninguna variable miembro ya que la estructura derivada deriva intencionalmente públicamente de la clase que especifique. La única advertencia es si la clase especificada no tiene un dtor virtual, sino algunos funcs virtuales (en cuyo caso la clase especificada sigue siendo polimórfica) aparte del problema de relleno. Boost supone que una clase polimórfica define un dtor virtual y maneja el relleno usando algunos detalles del compilador, supongo. – Abhay

-1

Estoy un poco confundido aquí, y espero obtener algunos comentarios sobre esta respuesta explicando lo que me estoy perdiendo.

Seguramente si desea saber si una clase es polimórfica, todo lo que tiene que hacer es preguntar si es compatible con dynamic_cast, ¿no es así?

template<class T, class> struct is_polymorphic_impl : false_type {}; 
template<class T> struct is_polymorphic_impl 
    <T, decltype(dynamic_cast<void*>(declval<T*>()))> : true_type {}; 

template<class T> struct is_polymorphic : 
    is_polymorphic_impl<remove_cv_t<T>, void*> {}; 

¿Alguien puede señalar un error en esta implementación? Imagino que debe haber uno, o debe haber sido uno en algún momento del pasado, porque the Boost documentation continúa afirmando que is_polymorphic "no se puede implementar de forma portátil en el lenguaje C++".

Pero "portably" es una especie de palabra de comadreja, ¿verdad? Tal vez solo están aludiendo a cómo MSVC no admite expresiones-SFINAE, o algunos dialectos como Embedded C++ no son compatibles con dynamic_cast. Tal vez cuando dicen "el lenguaje C++" quieren decir "un subconjunto de denominador común más bajo del lenguaje C++". Pero tengo una persistente sospecha de que tal vez se refieren a lo que dicen, y Soy al que le falta algo.

El enfoque typeid en el PO (modificada por una respuesta posterior para utilizar no un lvalue un valor p) también parece estar bien, pero por supuesto que no es constexpr y requiere en realidad la construcción de un T, que puede ser muy cara. Entonces, este enfoque dynamic_cast parece mejor ... a menos que no funcione por alguna razón. ¿Pensamientos?

Cuestiones relacionadas