2010-11-26 22 views
6

Próbuję zaimplementować pewien mechanizm w C++, w którym wszystkie klasy pochodzące od wspólnej klasy bazowej mają przypisany unikalny "identyfikator klasy". Na przykład:TypeID dla klas pochodnych wspólnej klasy bazowej

class BaseClass 
{ 
    //... 
    public: unsigned int GetID(void); 
    //... 
}; 
class DerivedClass : public BaseClass 
{ 
} 

klasy DerivedClass i wszystkich innych dzieci z klasy bazowej, powinna być w stanie wrócić unikalnych identyfikatorów bez dodatkowego kodu dodany do DerivedClass ... C++ czyni to raczej trudne dla mnie jednak. Wszelkie pomysły będą mile widziane.

Z góry dziękuję! --- Dan

+0

jeśli identyfikator ma być niepowtarzalny, np. ten zwrócony przez typeid. Czy twoja baza jest polimorficzna? – Chubsdad

+0

Moja klasa bazowa IS jest polimorficzna, ale z powodu braku umiejętności znalezienia solidnego i dokładnego wyjaśnienia co do dokładnej natury napięć towarzyszących RTTI (zarówno pod względem wydajności, jak i pamięci), staram się tego wszystkiego całkowicie uniknąć. . – Dan

Odpowiedz

2

Nie oznacza to, że znasz typeid i dynamic_cast.

Są szanse, że rozwiązują one twój problem.

Jeśli tak nie jest, proszę podać powód, dla którego nie.

Cheers & HTH.,

+0

Czy to nie jest komentarz? – Chubsdad

+0

Przepraszam, zapomniałem wspomnieć: starając się unikać RTTI, jeśli to możliwe, ponieważ słyszałem różne historie dotyczące tego, jak duży jest to wpływ i nie chcę ryzykować, ponieważ wydajność jest krytyczna dla mojej aplikacji. – Dan

+0

@Chubstad: nie, to nie jest komentarz. 'typid' jest ogólną odpowiedzią, gdy chcesz mieć identyfikator typu. możesz nawet wywnioskować to z nazwy. –

1

Jak powiada Alf, to nie powinno być konieczne. typeid podaje już unikalny identyfikator klasy, mimo że identyfikator nie jest liczbą całkowitą. Tylko dla śmiechu, jeśli wolno mi odpocząć „wspólnego klasy bazowej” warunek następnie:

inline unsigned int counter() { 
    static unsigned int count = 0; 
    return ++count; 
} 

struct BaseClass { 
    virtual unsigned int GetID() = 0; 
    virtual ~BaseClass() {} 
}; 

template <typename D> 
struct IntermediateClass : BaseClass { 
    virtual unsigned int GetID() { 
     static unsigned int thisid = counter(); 
     return thisid; 
    } 
}; 

// usage 
struct Derived : IntermediateClass<Derived> { 
    ... 
}; 

Można by trzeba dodać wątku bezpieczeństwa w counter jeśli to mają być wykorzystane w programach wielowątkowych.

Oczywiście identyfikator jest unikalny tylko w ramach danego programu.

To staje się trochę owłosione, jeśli twoja hierarchia dziedziczenia jest głęboka i jeśli masz dużo konstruktorów z różnymi sygnaturami dla różnych klas, ponieważ musisz wstawić IntermediateClass pomiędzy każdą klasą pochodną i jej bezpośrednią klasą bazową. Ale zawsze można wyskoczyć wszystkiego, co następuje:

inline unsigned int counter() { 
    static unsigned int count = 0; 
    return ++count; 
} 

struct BaseClass { 
    virtual unsigned int GetID() = 0; 
    virtual ~BaseClass() {} 
}; 

template <typename D> 
unsigned int ThisID(const D *) { 
    static unsigned int thisid = counter(); 
    return thisid; 
} 

// usage 
struct Derived : BaseClass { 
    // this single line pasted in each derived class 
    virtual unsigned int GetID() { return ThisID(this); } 
    ... 
}; 

Chyba Tam jest „okazja” dla nowej funkcji językowych tutaj: funkcja wirtualnego, który jest zdefiniowany w klasie bazowej jako funkcję szablonu z jednym " typename "parametr szablonu, który jest automatycznie zastępowany w każdej pochodnej klasie, używając tej pochodnej klasy jako argumentu szablonu. składnia wyimaginowany, ponieważ funkcje wirtualne szablonu są nielegalne:

struct BaseClass { 
    template <typename Derived> 
    virtual unsigned int GetID() { 
     static unsigned int thisid = counter(); 
     return thisid; 
    } 
    virtual ~BaseClass() {} 
}; 

Trudno uzasadnić funkcję języka w oparciu o chęć ponownego wdrożenia RTTI sami, umysł ...

+0

To działa dobrze, ale tracę polimorfizm mojej klasy, ponieważ dwie pochodne klasy dziedziczą teraz z dwóch różnych klas. – Dan

+0

@Dan: Wkurzyłem się trochę, ponieważ powiedziałeś, że twoje klasy były polimorficzne. W pierwszej opcji wszystko pochodzi pośrednio z BaseClass, więc nadal możesz używać obiektów pochodnych poprzez BaseClass *. W drugiej opcji wszystko pochodzi bezpośrednio od BaseClass, co znacznie ułatwia zarządzanie konstruktorami, ale musisz dodać jedną (identyczną) funkcję do każdej wyprowadzonej klasy. –

+0

Szczury, właśnie uświadomiły sobie, że linia w każdej wyprowadzonej klasie nie jest identyczna - musi ona wymienić klasę, aby ujednoznacznić wywołanie w przypadku, gdy jedna z tych pochodnych klas rozszerza kolejną, a zatem dziedziczy dwie funkcje GetIDImpl. Zmienię tę opcję. –

0

ta wymaga modyfikacji klas pochodnych , ale jeśli brak zaufania do RTTI jest jedyną przyczyną uniknięcia dobrze ustalonej trasy typu, dynamic_cast, TO

Coś takiego powinno być dobrym wyborem. Zwraca również "int" w porównaniu do "type_info" przez RTTI.

class BaseClass{ 
    //... 
    public: 
     virtual unsigned int GetID(void); 
    //... 
}; 

class DerivedClass : public BaseClass{ 
public: 
    virtual unsigned int GetID(void); 
}; 
+0

Próbuję uniknąć oddzielnej implementacji dla każdej DerivedClass; to jest mój problem. Przepraszam, jeśli nie wyjaśniłem tego. – Dan

2

Należy słuchać Alf :) Oto moja analiza: w czystym świecie, identyfikacja wdrożeń grozi polimorfizm funkcję wirtualną, nie może być wymagane, a nie powinna być konieczna.

w brudnym świecie rzeczywistym programowaniu, może masz jakieś powody, dla jednoznacznej identyfikacji, takie jak Organizowanie danych na dysku, identyfikowania komunikatów diagnostycznych, śledzenie przepływu sterowania, gromadzenia statystyk użytkowania itp

Jeśli twoje myśli są czyste twój projekt jest zły. Odejdź i zastanów się, dlaczego nie możesz wymagać unikalnego identyfikatora.

Jeśli myśli są skorumpowane przez rzeczywistość, to jesteś już gotowy zapłacić cenę za wydajność i pamięć, aby spełnić Twoje wymagania, a następnie według specyfikacji koszt używania wbudowanych funkcji językowych jest warty zapłaty, ponieważ jest w zasadzie jedynym sposobem na osiągnięcie celu, jakim jest zapewnienie nieinwazyjnej usługi identyfikacji. Przez nieinwazyjne mam na myśli, że nie musisz dodawać niczego do każdej klasy pochodnej. Oczywiście musisz coś dodać, więc jeśli nie chcesz tego zrobić, nie masz wielkiego wyboru, tylko zaakceptować to, co kompilator dodaje.

Głównym problemem jest to, że jeśli używasz dynamicznie ładowanych bibliotek współdzielonych (DLLS), RTTI może nie działać zgodnie z oczekiwaniami. To nie tylko wpływa negatywnie na typids, ale może również zapobiec wychwyceniu wyjątków, które możesz złapać (zostałem ugryziony!). Pewna ostrożność może być potrzebna, aby zapewnić unikalne vtables i inne RTTI. Może to oznaczać na przykład, że jeśli vtables podpinają twój destruktor, nie jest on wbudowany, ponieważ może on zostać wygenerowany w więcej niż jednym miejscu w tym przypadku, niszcząc wyjątkowość. Niektóre hakowanie może być konieczne w przypadku braku obsługi standardu ISO w dynamicznym ładowaniu.

+0

Wydaje się, że wiesz sporo o wewnętrznych działaniach RTTI. Czy istnieje w Internecie, że wiesz, że ma dokładny opis mechanizmów używanych na poziomie kompilatora do realizacji funkcji? – Dan

+0

Nie mam przydanych odnośników, ale miejscami do odwiedzenia są oczywiście strony rozwojowe gcc i specyfikacje ABI w branży, pierwotnie wyprodukowane przez AMD, ale teraz zarządzane przez grupę branżową. To w zasadzie określa API używane dla procesorów x86_64 dla różnych funkcji C++, w tym przekazywanie argumentów w rejestrach, obsługa wyjątków, dynamiczne powiązania itp. – Yttrill

Powiązane problemy