Teste se uma classe é polimórfica
-
12-09-2019 - |
Pergunta
Temos 'commonUtils' um sub-projeto que tem muitos code-snippets genéricos utilizados em todo o projeto pai. Uma dessas coisas interessantes que eu vi foi: -
/*********************************************************************
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;
}
Eu acreditava que o comentário e pensei que é bastante um modelo interessante embora não seja usado em todo o projeto. Eu tentei usá-lo como esta só por curiosidade ...
class PolyBase {
public:
virtual ~PolyBase(){}
};
class NPolyBase {
public:
~NPolyBase(){}
};
if (isPolymorphic<PolyBase>())
std::cout<<"PolyBase = Polymorphic\n";
if (isPolymorphic<NPolyBase>())
std::cout<<"NPolyBase = Also Polymorphic\n";
Mas nenhum deles jamais retorna verdadeiro. MSVC 2005 dá nenhum aviso, mas Comeau adverte expressão typeid não tem efeito. Seção 5.2.8 no C ++ padrão não diz nada parecido com o que diz o comentário ou seja typeid está é avaliada em tempo de compilação para tipos não polimórficos e em tempo de execução para tipos polimórficos.
1) Então eu acho que o comentário é enganosa / plain-errado ou desde que o autor deste código é bastante C sênior ++ programador, estou faltando alguma coisa?
2) OTOH, eu estou querendo saber se podemos testar se uma classe é polimórfico (tem pelo menos uma função virtual) usando alguma técnica?
3) Em que um quer saber se uma classe é polimórfica? Palpite ousado; para obter o start-endereço de uma classe usando dynamic_cast<void*>(T)
(como dynamic_cast
funciona apenas em classes polimórficas).
Aguardando as suas opiniões.
Agradecemos antecipadamente,
Solução
Eu não posso imaginar qualquer maneira possível como isso typeid poderia ser usado para verificar que tipo é polimórfico. Ele não pode ser usado até mesmo para afirmar que ele é, desde typeid irá funcionar em qualquer tipo. Impulso tem uma implementação aqui . Quanto à razão, pode ser necessário - um caso que conheço é a biblioteca Boost.Serialization. Se você está salvando tipo não polimórficos, então você pode apenas salvá-lo. Se salvar um polimórfico, você tem que recebe o seu tipo dinâmico usando typeid, e em seguida, chamar método de serialização para esse tipo (olhando-o em alguma tabela).
Atualizar : parece que eu sou realmente errado. Considere esta variante:
template <class T>
bool isPolymorphic() {
bool answer=false;
T *t = new T();
typeid(answer=true,*t);
delete t;
return answer;
}
Este realmente funciona como o nome sugere, exatamente por comentário no seu trecho de código original. A expressão dentro typeid não é avaliada se for "não designa uma Ivalue de tipo classe polimórfico" (STD 3,2 / 2). Assim, no caso acima, se T não é polimórfico, a expressão typeid não é avaliada. Se T é polimórfica, então * t é realmente lValue do tipo polimórfico, por isso expressão inteira tem que ser avaliado.
Agora, o seu exemplo original ainda está errado :-). Ele T()
usado, não *t
. E T()
criar rvalue (STD 3.10 / 6). Então, ele ainda produz uma expressão que não é "lvalue de classe polimórfica".
Isso é truque bastante interessante. Por outro lado, seu valor prático é um pouco limitado - porque enquanto boost :: is_polymorphic dá-lhe uma constante em tempo de compilação, este dá-lhe um valor de tempo de execução, para que você não pode instanciar código diferente para tipos polimórficos e não polimórficos .
Outras dicas
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;
}
Desde C ++ 11, este está agora disponível no cabeçalho <type_traits>
como std::is_polymorphic
. Ele pode ser usado como este:
struct PolyBase {
virtual ~PolyBase() {}
};
struct NPolyBase {
~NPolyBase() {}
};
if (std::is_polymorphic<PolyBase>::value)
std::cout << "PolyBase = Polymorphic\n";
if (std::is_polymorphic<NPolyBase>::value)
std::cout << "NPolyBase = Also Polymorphic\n";
Isto imprime apenas "polybase = polimórficos".
Pode-se usar os fatos que:
-
dynamic_cast
falhar em tempo de compilação, se o argumento não é uma classe polimórfica. Para que ele possa ser usado com SFINAE. -
dynamic_cast<void*>
é um elenco válida que retorna o endereço do completo objeto polymorpic.
Assim, em C ++ 11:
#include <iostream>
#include <type_traits>
template<class T>
auto is_polymorphic2_test(T* p) -> decltype(dynamic_cast<void*>(p), std::true_type{});
template<class T>
auto is_polymorphic2_test(...) -> std::false_type;
template<class T>
using is_polymorphic2 = decltype(is_polymorphic2_test<T>(static_cast<T*>(0)));
struct A {};
struct B { virtual ~B(); };
int main() {
std::cout << is_polymorphic2<A>::value << '\n'; // Outputs 0.
std::cout << is_polymorphic2<B>::value << '\n'; // Outputs 1.
}
Estou um pouco confuso aqui, e estou esperando para obter alguns comentários sobre esta resposta explicando o que eu estou sentindo falta.
Certamente, se você quiser saber se uma classe é polimórfica, tudo que você tem a fazer é perguntar se ele suporta dynamic_cast
, não é mesmo?
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*> {};
Alguém pode apontar uma falha neste implementação? Eu imagino que deve haver um, ou deve ter sido um em algum ponto no passado, porque a documentação impulso continua a afirmar que is_polymorphic
"não pode ser portably implementado na linguagem C ++".
Mas "portably" é uma espécie de palavra doninha, certo? Talvez eles estão apenas aludindo à forma como MSVC não suporta expressão-SFINAE, ou alguns dialetos, como Incorporado C ++ não dynamic_cast
apoio. Talvez quando eles dizem "a linguagem C ++" que significa "um subconjunto menor denominador comum da linguagem C ++." Mas eu tenho uma suspeita persistente de que talvez eles querem dizer o que dizem, e Eu sou o único que está faltando alguma coisa.
A abordagem typeid
no OP (alterada por uma resposta mais tarde usar um lvalue não um rvalue) também parece muito bem, mas é claro que não é constexpr e que exige realmente construir um T
, que pode ser super caro. Portanto, esta abordagem dynamic_cast
parece melhor ... a menos que ele não funciona por algum motivo. Pensamentos?