programming oriented concepts c++ oop

oriented - C++ equivalente de instanceof



object oriented programming c++ pdf (6)

Instanceof implementación sin dynamic_cast

Creo que esta pregunta todavía es relevante hoy. Usando el estándar C ++ 11 ahora puede implementar una instanceof función sin usar dynamic_cast como este:

if (dynamic_cast<B*>(aPtr) != nullptr) { // aPtr is instance of B } else { // aPtr is NOT instance of B }

Pero todavía RTTI soporte RTTI . Así que aquí está mi solución para este problema dependiendo de algunas Macros y Magia de Metaprogramación. El único inconveniente es que este enfoque no funciona para la herencia múltiple .

InstanceOfMacros.h

#include <set> #include <tuple> #include <typeindex> #define _EMPTY_BASE_TYPE_DECL() using BaseTypes = std::tuple<>; #define _BASE_TYPE_DECL(Class, BaseClass) / using BaseTypes = decltype(std::tuple_cat(std::tuple<BaseClass>(), Class::BaseTypes())); #define _INSTANCE_OF_DECL_BODY(Class) / static const std::set<std::type_index> baseTypeContainer; / virtual bool instanceOfHelper(const std::type_index &_tidx) { / if (std::type_index(typeid(ThisType)) == _tidx) return true; / if (std::tuple_size<BaseTypes>::value == 0) return false; / return baseTypeContainer.find(_tidx) != baseTypeContainer.end(); / } / template <typename... T> / static std::set<std::type_index> getTypeIndexes(std::tuple<T...>) { / return std::set<std::type_index>{std::type_index(typeid(T))...}; / } #define INSTANCE_OF_SUB_DECL(Class, BaseClass) / protected: / using ThisType = Class; / _BASE_TYPE_DECL(Class, BaseClass) / _INSTANCE_OF_DECL_BODY(Class) #define INSTANCE_OF_BASE_DECL(Class) / protected: / using ThisType = Class; / _EMPTY_BASE_TYPE_DECL() / _INSTANCE_OF_DECL_BODY(Class) / public: / template <typename Of> / typename std::enable_if<std::is_base_of<Class, Of>::value, bool>::type instanceOf() { / return instanceOfHelper(std::type_index(typeid(Of))); / } #define INSTANCE_OF_IMPL(Class) / const std::set<std::type_index> Class::baseTypeContainer = Class::getTypeIndexes(Class::BaseTypes());

Manifestación

Luego puede usar esto ( con precaución ) de la siguiente manera:

DemoClassHierarchy.hpp *

#include "InstanceOfMacros.h" struct A { virtual ~A() {} INSTANCE_OF_BASE_DECL(A) }; INSTANCE_OF_IMPL(A) struct B : public A { virtual ~B() {} INSTANCE_OF_SUB_DECL(B, A) }; INSTANCE_OF_IMPL(B) struct C : public A { virtual ~C() {} INSTANCE_OF_SUB_DECL(C, A) }; INSTANCE_OF_IMPL(C) struct D : public C { virtual ~D() {} INSTANCE_OF_SUB_DECL(D, C) }; INSTANCE_OF_IMPL(D)

El siguiente código presenta una pequeña demostración para verificar el comportamiento correcto y rudimentario.

InstanceOfDemo.cpp

#include <iostream> #include <memory> #include "DemoClassHierarchy.hpp" int main() { A *a2aPtr = new A; A *a2bPtr = new B; std::shared_ptr<A> a2cPtr(new C); C *c2dPtr = new D; std::unique_ptr<A> a2dPtr(new D); std::cout << "a2aPtr->instanceOf<A>(): expected=1, value=" << a2aPtr->instanceOf<A>() << std::endl; std::cout << "a2aPtr->instanceOf<B>(): expected=0, value=" << a2aPtr->instanceOf<B>() << std::endl; std::cout << "a2aPtr->instanceOf<C>(): expected=0, value=" << a2aPtr->instanceOf<C>() << std::endl; std::cout << "a2aPtr->instanceOf<D>(): expected=0, value=" << a2aPtr->instanceOf<D>() << std::endl; std::cout << std::endl; std::cout << "a2bPtr->instanceOf<A>(): expected=1, value=" << a2bPtr->instanceOf<A>() << std::endl; std::cout << "a2bPtr->instanceOf<B>(): expected=1, value=" << a2bPtr->instanceOf<B>() << std::endl; std::cout << "a2bPtr->instanceOf<C>(): expected=0, value=" << a2bPtr->instanceOf<C>() << std::endl; std::cout << "a2bPtr->instanceOf<D>(): expected=0, value=" << a2bPtr->instanceOf<D>() << std::endl; std::cout << std::endl; std::cout << "a2cPtr->instanceOf<A>(): expected=1, value=" << a2cPtr->instanceOf<A>() << std::endl; std::cout << "a2cPtr->instanceOf<B>(): expected=0, value=" << a2cPtr->instanceOf<B>() << std::endl; std::cout << "a2cPtr->instanceOf<C>(): expected=1, value=" << a2cPtr->instanceOf<C>() << std::endl; std::cout << "a2cPtr->instanceOf<D>(): expected=0, value=" << a2cPtr->instanceOf<D>() << std::endl; std::cout << std::endl; std::cout << "c2dPtr->instanceOf<A>(): expected=1, value=" << c2dPtr->instanceOf<A>() << std::endl; std::cout << "c2dPtr->instanceOf<B>(): expected=0, value=" << c2dPtr->instanceOf<B>() << std::endl; std::cout << "c2dPtr->instanceOf<C>(): expected=1, value=" << c2dPtr->instanceOf<C>() << std::endl; std::cout << "c2dPtr->instanceOf<D>(): expected=1, value=" << c2dPtr->instanceOf<D>() << std::endl; std::cout << std::endl; std::cout << "a2dPtr->instanceOf<A>(): expected=1, value=" << a2dPtr->instanceOf<A>() << std::endl; std::cout << "a2dPtr->instanceOf<B>(): expected=0, value=" << a2dPtr->instanceOf<B>() << std::endl; std::cout << "a2dPtr->instanceOf<C>(): expected=1, value=" << a2dPtr->instanceOf<C>() << std::endl; std::cout << "a2dPtr->instanceOf<D>(): expected=1, value=" << a2dPtr->instanceOf<D>() << std::endl; delete a2aPtr; delete a2bPtr; delete c2dPtr; return 0; }

Salida:

a2aPtr->instanceOf<A>(): expected=1, value=1 a2aPtr->instanceOf<B>(): expected=0, value=0 a2aPtr->instanceOf<C>(): expected=0, value=0 a2aPtr->instanceOf<D>(): expected=0, value=0 a2bPtr->instanceOf<A>(): expected=1, value=1 a2bPtr->instanceOf<B>(): expected=1, value=1 a2bPtr->instanceOf<C>(): expected=0, value=0 a2bPtr->instanceOf<D>(): expected=0, value=0 a2cPtr->instanceOf<A>(): expected=1, value=1 a2cPtr->instanceOf<B>(): expected=0, value=0 a2cPtr->instanceOf<C>(): expected=1, value=1 a2cPtr->instanceOf<D>(): expected=0, value=0 c2dPtr->instanceOf<A>(): expected=1, value=1 c2dPtr->instanceOf<B>(): expected=0, value=0 c2dPtr->instanceOf<C>(): expected=1, value=1 c2dPtr->instanceOf<D>(): expected=1, value=1 a2dPtr->instanceOf<A>(): expected=1, value=1 a2dPtr->instanceOf<B>(): expected=0, value=0 a2dPtr->instanceOf<C>(): expected=1, value=1 a2dPtr->instanceOf<D>(): expected=1, value=1

Actuación

La pregunta más interesante que surge ahora es si esta cosa malvada es más eficiente que el uso de dynamic_cast . Por lo tanto, he escrito una aplicación de medición del rendimiento muy básica.

InstanceOfPerformance.cpp

#include <chrono> #include <iostream> #include <string> #include "DemoClassHierarchy.hpp" template <typename Base, typename Derived, typename Duration> Duration instanceOfMeasurement(unsigned _loopCycles) { auto start = std::chrono::high_resolution_clock::now(); volatile bool isInstanceOf = false; for (unsigned i = 0; i < _loopCycles; ++i) { Base *ptr = new Derived; isInstanceOf = ptr->template instanceOf<Derived>(); delete ptr; } auto end = std::chrono::high_resolution_clock::now(); return std::chrono::duration_cast<Duration>(end - start); } template <typename Base, typename Derived, typename Duration> Duration dynamicCastMeasurement(unsigned _loopCycles) { auto start = std::chrono::high_resolution_clock::now(); volatile bool isInstanceOf = false; for (unsigned i = 0; i < _loopCycles; ++i) { Base *ptr = new Derived; isInstanceOf = dynamic_cast<Derived *>(ptr) != nullptr; delete ptr; } auto end = std::chrono::high_resolution_clock::now(); return std::chrono::duration_cast<Duration>(end - start); } int main() { unsigned testCycles = 10000000; std::string unit = " us"; using DType = std::chrono::microseconds; std::cout << "InstanceOf performance(A->D) : " << instanceOfMeasurement<A, D, DType>(testCycles).count() << unit << std::endl; std::cout << "InstanceOf performance(A->C) : " << instanceOfMeasurement<A, C, DType>(testCycles).count() << unit << std::endl; std::cout << "InstanceOf performance(A->B) : " << instanceOfMeasurement<A, B, DType>(testCycles).count() << unit << std::endl; std::cout << "InstanceOf performance(A->A) : " << instanceOfMeasurement<A, A, DType>(testCycles).count() << unit << "/n" << std::endl; std::cout << "DynamicCast performance(A->D) : " << dynamicCastMeasurement<A, D, DType>(testCycles).count() << unit << std::endl; std::cout << "DynamicCast performance(A->C) : " << dynamicCastMeasurement<A, C, DType>(testCycles).count() << unit << std::endl; std::cout << "DynamicCast performance(A->B) : " << dynamicCastMeasurement<A, B, DType>(testCycles).count() << unit << std::endl; std::cout << "DynamicCast performance(A->A) : " << dynamicCastMeasurement<A, A, DType>(testCycles).count() << unit << "/n" << std::endl; return 0; }

Los resultados varían y se basan esencialmente en el grado de optimización del compilador. Compilando el programa de medición de rendimiento usando g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cpp la salida en mi máquina local fue:

InstanceOf performance(A->D) : 699638 us InstanceOf performance(A->C) : 642157 us InstanceOf performance(A->B) : 671399 us InstanceOf performance(A->A) : 626193 us DynamicCast performance(A->D) : 754937 us DynamicCast performance(A->C) : 706766 us DynamicCast performance(A->B) : 751353 us DynamicCast performance(A->A) : 676853 us

Mhm, este resultado fue muy aleccionador, porque los tiempos demuestran que el nuevo enfoque no es mucho más rápido en comparación con el enfoque dynamic_cast . Es incluso menos eficiente para el caso de prueba especial que prueba si un puntero de A es una instancia de A PERO la marea cambia al sintonizar nuestro binario usando la otpimización del compilador. El comando de compilación respectivo es g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp . El resultado en mi máquina local fue increíble:

InstanceOf performance(A->D) : 3035 us InstanceOf performance(A->C) : 5030 us InstanceOf performance(A->B) : 5250 us InstanceOf performance(A->A) : 3021 us DynamicCast performance(A->D) : 666903 us DynamicCast performance(A->C) : 698567 us DynamicCast performance(A->B) : 727368 us DynamicCast performance(A->A) : 3098 us

Si no depende de la herencia múltiple, no se opone a las viejas macros C, RTTI y metaprogramación de plantillas y no es demasiado flojo para agregar algunas instrucciones pequeñas a las clases de su jerarquía de clases, entonces este enfoque puede impulsar su aplicación un poco con respecto a su rendimiento, si a menudo termina verificando la instancia de un puntero. Pero úsalo con precaución . No hay garantía para la corrección de este enfoque.

Nota: Todas las demos fueron compiladas usando clang (Apple LLVM version 9.0.0 (clang-900.0.39.2)) bajo macOS Sierra en una MacBook Pro Mid 2012.

Edición: también probé el rendimiento en una máquina Linux usando gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 . En esta plataforma, el beneficio de rendimiento no fue tan significativo como en macOs con clang.

Salida (sin optimización del compilador):

InstanceOf performance(A->D) : 390768 us InstanceOf performance(A->C) : 333994 us InstanceOf performance(A->B) : 334596 us InstanceOf performance(A->A) : 300959 us DynamicCast performance(A->D) : 331942 us DynamicCast performance(A->C) : 303715 us DynamicCast performance(A->B) : 400262 us DynamicCast performance(A->A) : 324942 us

Salida (con optimización del compilador):

InstanceOf performance(A->D) : 209501 us InstanceOf performance(A->C) : 208727 us InstanceOf performance(A->B) : 207815 us InstanceOf performance(A->A) : 197953 us DynamicCast performance(A->D) : 259417 us DynamicCast performance(A->C) : 256203 us DynamicCast performance(A->B) : 261202 us DynamicCast performance(A->A) : 193535 us

¿Cuál es el método preferido para lograr el equivalente de C ++ de instanceof ?


Dependiendo de lo que quieras hacer, podrías hacer esto:

template<typename Base, typename T> inline bool instanceof(const T*) { return std::is_base_of<Base, T>::value; }

Utilizar:

if (instanceof<BaseClass>(ptr)) { ... }

Sin embargo, esto opera puramente en los tipos conocidos por el compilador.

Editar:

Este código debería funcionar para punteros polimórficos:

template<typename Base, typename T> inline bool instanceof(const T *ptr) { return dynamic_cast<const Base*>(ptr) != nullptr; }

Ejemplo: http://cpp.sh/6qir


Esto funcionó perfecto para mí usando Code :: Blocks IDE con GCC complier

#include<iostream> #include<typeinfo> #include<iomanip> #define SIZE 20 using namespace std; class Publication { protected: char title[SIZE]; int price; public: Publication() { cout<<endl<<" Enter title of media : "; cin>>title; cout<<endl<<" Enter price of media : "; cin>>price; } virtual void show()=0; }; class Book : public Publication { int pages; public: Book() { cout<<endl<<" Enter number of pages : "; cin>>pages; } void show() { cout<<endl<<setw(12)<<left<<" Book Title"<<": "<<title; cout<<endl<<setw(12)<<left<<" Price"<<": "<<price; cout<<endl<<setw(12)<<left<<" Pages"<<": "<<pages; cout<<endl<<" ----------------------------------------"; } }; class Tape : public Publication { int duration; public: Tape() { cout<<endl<<" Enter duration in minute : "; cin>>duration; } void show() { cout<<endl<<setw(10)<<left<<" Tape Title"<<": "<<title; cout<<endl<<setw(10)<<left<<" Price"<<": "<<price; cout<<endl<<setw(10)<<left<<" Duration"<<": "<<duration<<" minutes"; cout<<endl<<" ----------------------------------------"; } }; int main() { int n, i, type; cout<<endl<<" Enter number of media : "; cin>>n; Publication **p = new Publication*[n]; cout<<endl<<" Enter "<<n<<" media details : "; for(i=0;i<n;i++) { cout<<endl<<" Select Media Type [ 1 - Book / 2 - Tape ] "; cin>>type; if ( type == 1 ) { p[i] = new Book(); } else if ( type == 2 ) { p[i] = new Tape(); } else { i--; cout<<endl<<" Invalid type. You have to Re-enter choice"; } } for(i=0;i<n;i++) { if ( typeid(Book) == typeid(*p[i]) ) { p[i]->show(); } } return 0; }


Intenta usar:

if(NewType* v = dynamic_cast<NewType*>(old)) { // old was safely casted to NewType v->doSomething(); }

Esto requiere que su compilador tenga habilitada la compatibilidad con rtti.

EDITAR: ¡He tenido algunos buenos comentarios sobre esta respuesta!

Cada vez que necesite usar un dynamic_cast (o instanceof), será mejor que se pregunte si es necesario. En general, es un signo de diseño deficiente.

Las soluciones típicas es poner el comportamiento especial para la clase que está buscando en una función virtual en la clase base o tal vez introducir algo como un visitor donde puede introducir un comportamiento específico para las subclases sin cambiar la interfaz (excepto para agregar la interfaz de aceptación del visitante de curso).

Como se señaló, dynamic_cast no es gratis. Un hack de ejecución simple y consistente que maneja la mayoría (pero no todos los casos) básicamente está agregando una enumeración que representa todos los tipos posibles que su clase puede tener y compruebe si obtuvo la correcta.

if(old->getType() == BOX) { Box* box = static_cast<Box*>(old); // Do something box specific }

Esto no es bueno para el diseño, pero puede ser una solución y su costo es más o menos solo una llamada de función virtual. También funciona independientemente de que RTTI esté habilitado o no.

Tenga en cuenta que este enfoque no admite múltiples niveles de herencia, por lo que si no tiene cuidado, puede terminar con un código como este:

// Here we have a SpecialBox class that inherits Box, since it has its own type // we must check for both BOX or SPECIAL_BOX if(old->getType() == BOX || old->getType() == SPECIAL_BOX) { Box* box = static_cast<Box*>(old); // Do something box specific }


Se sabe que dynamic_cast es ineficiente. Atraviesa la jerarquía de herencia, y es la única solución si tiene múltiples niveles de herencia, y necesita comprobar si un objeto es una instancia de cualquiera de los tipos en su jerarquía de tipos.

Pero si una forma de instanceof más limitada que solo verifica si un objeto es exactamente del tipo que usted especifica, es suficiente para sus necesidades, la función siguiente sería mucho más eficiente:

template<typename T, typename K> inline bool isType(const K &k) { return typeid(T).hash_code() == typeid(k).hash_code(); }

Aquí hay un ejemplo de cómo invocaría la función anterior:

DerivedA k; Base *p = &k; cout << boolalpha << isType<DerivedA>(*p) << endl; // true cout << boolalpha << isType<DerivedB>(*p) << endl; // false

Especificaría el tipo de plantilla A (como el tipo que está comprobando) y pasaría el objeto que desea probar como el argumento (del que se deduciría el tipo de plantilla K ).


#include <iostream.h> #include<typeinfo.h> template<class T> void fun(T a) { if(typeid(T) == typeid(int)) { //Do something cout<<"int"; } else if(typeid(T) == typeid(float)) { //Do Something else cout<<"float"; } } void main() { fun(23); fun(90.67f); }