Jaka jest preferowana metoda uzyskania odpowiednika języka Java w języku C ++ instanceof
?
Jaka jest preferowana metoda uzyskania odpowiednika języka Java w języku C ++ instanceof
?
Odpowiedzi:
Spróbuj użyć:
if(NewType* v = dynamic_cast<NewType*>(old)) {
// old was safely casted to NewType
v->doSomething();
}
Wymaga to włączenia w kompilatorze obsługi rtti.
EDYCJA: Mam kilka dobrych komentarzy na temat tej odpowiedzi!
Za każdym razem, gdy potrzebujesz użyć dynamicznej transmisji (lub instanceof), lepiej zadaj sobie pytanie, czy jest to konieczne. Zazwyczaj jest to oznaka złego projektu.
Typowe obejścia tego problemu polegają na umieszczeniu specjalnego zachowania dla klasy, którą sprawdzasz, w funkcji wirtualnej w klasie bazowej lub być może wprowadzeniu czegoś takiego jak gość, w którym możesz wprowadzić określone zachowanie dla podklas bez zmiany interfejsu (z wyjątkiem dodania interfejsu akceptacji przez kierunek).
Jak wskazano, dynamic_cast nie jest darmowy. Prostym i konsekwentnie działającym hackem, który obsługuje większość (ale nie wszystkie przypadki), jest w zasadzie dodanie wyliczenia reprezentującego wszystkie możliwe typy, jakie może mieć Twoja klasa i sprawdzenie, czy masz właściwy.
if(old->getType() == BOX) {
Box* box = static_cast<Box*>(old);
// Do something box specific
}
To nie jest dobry projekt z oo, ale może być obejściem, a jego koszt to mniej więcej wirtualne wywołanie funkcji. Działa również niezależnie od tego, czy RTTI jest włączony czy nie.
Pamiętaj, że to podejście nie obsługuje wielu poziomów dziedziczenia, więc jeśli nie będziesz ostrożny, możesz skończyć z kodem wyglądającym tak:
// 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
}
W zależności od tego, co chcesz zrobić, możesz to zrobić:
template<typename Base, typename T>
inline bool instanceof(const T*) {
return std::is_base_of<Base, T>::value;
}
Posługiwać się:
if (instanceof<BaseClass>(ptr)) { ... }
Działa to jednak wyłącznie na typach znanych kompilatorowi.
Edytować:
Ten kod powinien działać dla wskaźników polimorficznych:
template<typename Base, typename T>
inline bool instanceof(const T *ptr) {
return dynamic_cast<const Base*>(ptr) != nullptr;
}
Przykład: http://cpp.sh/6qir
Myślę, że to pytanie jest nadal aktualne. Korzystając ze standardu C ++ 11, możesz teraz zaimplementować instanceof
funkcję bez użycia dynamic_cast
takiego:
if (dynamic_cast<B*>(aPtr) != nullptr) {
// aPtr is instance of B
} else {
// aPtr is NOT instance of B
}
Ale nadal jesteś zależny od RTTI
wsparcia. Oto moje rozwiązanie tego problemu w zależności od niektórych makr i magii metaprogramowania. Jedyną wadą jest to, że takie podejście nie działa w przypadku wielokrotnego dziedziczenia .
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());
Możesz następnie użyć tych rzeczy ( ostrożnie ) w następujący sposób:
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)
Poniższy kod przedstawia małe demo w celu zweryfikowania szczątkowego prawidłowego zachowania.
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;
}
Wynik:
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
Najciekawsze pytanie, jakie się teraz pojawia, brzmi: czy te złe rzeczy są bardziej wydajne niż ich użycie dynamic_cast
. Dlatego napisałem bardzo podstawową aplikację do pomiaru wydajności.
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;
}
Wyniki różnią się i są zasadniczo oparte na stopniu optymalizacji kompilatora. Kompilowanie programu pomiaru wydajności przy użyciu g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cpp
danych wyjściowych na moim komputerze lokalnym było:
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, ten wynik był bardzo otrzeźwiający, ponieważ czasy pokazują, że nowe podejście nie jest dużo szybsze w porównaniu do dynamic_cast
podejścia. Jest nawet mniej wydajny dla specjalnego przypadku testowego, który sprawdza, czy wskaźnik A
jest instancją A
. ALE fala się zmienia, dostrajając nasz plik binarny za pomocą optymalizacji kompilatora. Odpowiednie polecenie kompilatora to g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp
. Wynik na mojej lokalnej maszynie był niesamowity:
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
Jeśli nie jesteś zależny od wielokrotnego dziedziczenia, nie jesteś przeciwnikiem starych dobrych makr C, RTTI i metaprogramowania szablonów i nie jesteś zbyt leniwy, aby dodać kilka małych instrukcji do klas hierarchii klas, to takie podejście może nieco poprawić twoją aplikację jeśli chodzi o jego wydajność, jeśli często kończy się na sprawdzeniu wystąpienia wskaźnika. Ale używaj go ostrożnie . Nie ma gwarancji na poprawność tego podejścia.
Uwaga: Wszystkie demonstracje zostały skompilowane przy użyciu clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))
MacOS Sierra na MacBooku Pro w połowie 2012 roku.
Edycja:
Testowałem również wydajność na komputerze z systemem Linux przy użyciu gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
. Na tej platformie korzyść związana z wydajnością nie była tak znacząca, jak w przypadku MacO z clang.
Dane wyjściowe (bez optymalizacji kompilatora):
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
Dane wyjściowe (z optymalizacją kompilatora):
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
dynamic_cast
wiadomo, że jest nieefektywny. Przechodzi przez hierarchię dziedziczenia i jest to jedyne rozwiązanie, jeśli masz wiele poziomów dziedziczenia i musisz sprawdzić, czy obiekt jest instancją dowolnego z typów w swojej hierarchii typów.
Ale jeśli bardziej ograniczona forma instanceof
tego sprawdza tylko, czy obiekt jest dokładnie taki, jaki określisz, wystarczy dla twoich potrzeb, funkcja poniżej byłaby o wiele bardziej wydajna:
template<typename T, typename K>
inline bool isType(const K &k) {
return typeid(T).hash_code() == typeid(k).hash_code();
}
Oto przykład wywołania powyższej funkcji:
DerivedA k;
Base *p = &k;
cout << boolalpha << isType<DerivedA>(*p) << endl; // true
cout << boolalpha << isType<DerivedB>(*p) << endl; // false
Podaj typ szablonu A
(jako typ, którego szukasz) i przekaż obiekt, który chcesz przetestować, jako argument (z którego typu szablonu K
byłby wywnioskowany).
#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);
}
instanceof
odpytuje typ dynamiczny, ale w tej odpowiedzi typ dynamiczny i statyczny zawsze odpowiadają.
Działa to idealnie dla mnie przy użyciu Code :: Blocks IDE z kompilatorem GCC
#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;
}
typeid
”, co wprawdzie jest błędne („Nie ma gwarancji, że do tej samej instancji std :: type_info przywołane zostaną wszystkie oceny wyrażenia typeid tego samego typu ... assert(typeid(A) == typeid(A)); /* not guaranteed */
”, patrz cppreference.com ), wskazuje, że przynajmniej próbował odpowiedzieć na pytanie, choć bezskutecznie, ponieważ zaniedbał zaoferowania minimalnego działającego przykładu.