2009-10-01 13 views
5

Mam dwa wskaźniki do obiektów i chcę przetestować, czy są one dokładnie tym samym obiektem w najbardziej niezawodny sposób. Nie chcę wyraźnie wywoływać przeciążenia operator == i chcę, aby działał bez względu na to, które klasy bazowe, wirtualne klasy bazowe i wiele dziedziczenia są używane.Co powinno być używane do sprawdzania tożsamości w C++?

Mój obecny kod jest taka:

((void*)a) == ((void*)b) 

I moim przypadku działa to. Jednak, że nie działa w tym przypadku:

class B1 {}; 
class B2 {}; 
class C : public B1, public B2 {} 

C c; 
B1 *a = &c; 
B2 *b = &c; 

zastępczą w reinterpert_cast, static_cast lub dynamic_cast nie działa albo.


Szczególnie mam nadzieję na coś, co kończy się naprawdę proste i skuteczne. Idealnie byłoby nie wymagać żadnych instrukcji rozgałęzień, które mogłyby zaimplementować i zrobiłyby coś takiego, dostosuj wskaźnik do początku obiektu i porównaj.

+0

Drugi przypadek nie działa, ponieważ wskaźniki są fizycznie różne adresy, każdy wskazujący na innej klasy bazowej przesunięcia wewnątrz obiektu pochodzi. Różni się to od kompilatora do kompilatora. –

Odpowiedz

8

Jeśli zajęcia są naprawdę dokładnie tak, jak podano to jest to niemożliwe, ponieważ nie ma wystarczająco dużo informacji dostępnych w czasie wykonywania aby zrekonstruować wymagane informacje.

Jeśli są to klasy polimorficzne, z funkcjami wirtualnymi, brzmi to tak, jakby odpowiedź brzmiała: dynamic_cast<void *>. Zwraca wskaźnik do najbardziej wyprowadzonego obiektu. Twój czek będzie wtedy dynamic_cast<void *>(a)==dynamic_cast<void *>(b).

Patrz paragraf 7 tutaj:

http://www.csci.csusb.edu/dick/c++std/cd2/expr.html#expr.dynamic.cast

podejrzewam zastosować zwykłe dynamic_cast kwestie - to znaczy, nie ma gwarancji, to będzie szybkie, a Twoje zajęcia będą musiały być polimorficzny.

To nie jest funkcja, której się użyłem, obawiam się - ale widziałem, że często sugerują ją ludzie, którzy ją mają, i wnioskuję, że jest ona szeroko wspierana i działa tak, jak reklamowano.

+0

Tak, nadal wymagałoby to, aby klasa była polimorficzna (tj. Miała vtable). –

+0

Ale to jest cena do zapłaty, powiedziałbym. I rzeczywiście, jeśli naprawdę ma wiele dziedziczenia, to brzmi, jakby i tak miał klasy polimorficzne. Jeśli nie, proste 'virtual ~ B1() {}' i to samo dla 'B2' będzie w porządku. +1 rzeczywiście - do cholery widziałem czerwony pasek niebezpieczeństwa, pisząc coś podobnego do tej odpowiedzi xD –

+0

Nie przyjmę żadnego rozwiązania w sprawie nie wirtualnej i wydaje się to najlepsze w przypadku wirtualnym. – BCS

1

Z wyjątkiem inteligentnych wskaźników (które tak naprawdę nie są wskaźnikami, ale obiektami klasy), przeciążanie operator== nie jest możliwe dla wskaźników, więc rzutowanie nie powinno być konieczne.

Oczywiście porównywanie wskaźników różnych typów może nie działać. Jak myślisz, dlaczego musisz to zrobić?

+0

Zgaduję, że próbują porównać dwa wskaźniki interfejsu, aby sprawdzić, czy wskazują na ten sam obiekt. –

+0

Poszukuję wzorca, którego mogę użyć w każdej sytuacji. – BCS

-2

Można sprawdzić, czy obiekty wskazywały na zachodzenie w pamięci (kradzież z odpowiedzi Marka Ransoma). To wywołuje niezdefiniowanej zachowań, ale należy robić to, co chcesz na innej uzasadnionej kompilatora:

template <typename T1, typename T2> 
bool is(const T1 *left, const T2 * right) 
{ 
    const char *left_begin=reinterpret_cast<const char*>(left); 
    const char *left_end=left_begin+sizeof(*left); 

    const char *right_begin=reinterpret_cast<const char*>(right); 
    const char *right_end=right_begin+sizeof(*right); 

    return (left_begin <= right_begin && right_begin < left_end) || 
      (right_begin <= left_begin && left_begin < right_end); 
} 
+1

Nie sądzę, że działałoby, gdyby odwołania były do ​​niezależnych klas bazowych, ponieważ będą one (myślę) być sąsiednich zamiast nakładających się na siebie – BCS

+0

To zwraca dla mnie false w Visual Studio 2005 dla podanego przykładu. Wartości to: left_begin = 1245027; left_end = 1245028; right_begin = 1245028; right_end = 1245029 – garethm

+1

Może to powodować problemy z wielokrotnym dziedziczeniem. – Managu

-1

Twoje podejście pracował dla mnie nawet w cytowanym przypadku:

class B1 {}; 
class B2 {}; 
class C : public B1, public B2 {}; 

int main() { 
    C c; 
    B1 *a = &c; 
    B2 *b = &c; 

if ((void*)a == (void*)b) { 
    printf("equal"); 
} 
else { 
    printf("not equal!"); 
} 

} 

drukuje „równe” tutaj ..

+0

Potrzebujesz funkcji wirtualnych w B1 i B2, aby zobaczyć różnicę. –

+0

co słychać? Dostaję to na GCC, ale zawiedzie pod DMC (z digitalmars) – BCS

+1

g ++ zrobił tam pustą optymalizację klasy bazowej. Po prostu przyklej pole "char" zarówno w "B1" i "B2". –

3

Nie ma ogólnego sposobu, aby to zrobić. Podobiekty klasy podstawowej, ogólnie rzecz biorąc, nie mają wiedzy o tym, że tak jest, więc jeśli masz tylko wskaźnik do obiektu podrzędnego klasy podstawowej, nie masz żadnych środków, aby uzyskać wskaźnik do najbardziej wyprowadzonego obiektu, do którego on należy, jeśli nie znamy rodzaju tego ostatniego z góry.

Zacznijmy od tego:

struct B1 { char b1; }; 
struct B2 { char b2; }; 
struct D : B1, B2 { char d; }; 

// and some other type... 
struct Z : B2, B1 { }; 

Rozważmy typową realizację układu w pamięci z D. W przypadku braku vtable, jedyne co mamy to surowe dane (i ewentualnie dopełnienia):

 Offset Field 
     ------ ----- 
    /  0 b1  >- B1 
D-<  1 b2  >- B2 
    \  2 d 

Masz dwa wskaźniki:

B1* p1; 
B2* p2; 

Każdy wskaźnik skutecznie wskazuje na jednym char ramach wystąpienie D. Jednakże, jeśli nie wiesz tego z góry, jak możesz powiedzieć? Istnieje również możliwość, że wskaźniki mogą raczej wskazywać na podobiekty w instancji o numerze Z i same patrząc na wartości wskaźników, nie ma oczywiście sposobu, aby powiedzieć; nie ma też niczego, co wy (lub kompilator) można wydedukować z danych przywoływanych przez wskaźnik, ponieważ jest to tylko jeden bajt danych w strukturze.

+0

Powiedziałbym, że w istocie, problem polega na tym, że w C++ '(B1 *) c' i' (B2 *) c' nie są tym samym obiektem, o ile te typy wiedzą, ale pytający chce Zdefiniuj "ten sam obiekt", co oznacza, że ​​są (ponieważ klasa C wie, że są). Domyślam się, że możesz zdefiniować wirtualną funkcję GetMostDerivedPtr we wszystkich swoich interfejsach i każda klasa ją implementuje (być może za pomocą helpera CRTP), aby zwrócić '(void *) this' (' (void *) static_cast (this) 'with pomocnik). –

+0

Cóż, są one podobiektami klasy bazowej tego samego obiektu najbardziej pochodnego, a znaczenie tego jest dobrze zdefiniowane w ISO C++ ... po prostu nie jest to coś, co można zrobić w ramach podanych ograniczeń. –

+0

Kompilator wie z góry, do którego podobiektu trafiają poszczególne wskaźniki, zakładając, że są podobiektami. Jeśli rzucisz oba te wskaźniki na D, wyjdą one równe. Jedynym problemem jest wprowadzenie pewnego typu, do którego oba mogą być rzutowane. Można tego dokonać za pomocą wirtualnej podstawy lub metaprogramowania szablonu. – Potatoswatter

0

Poszukujesz rozwiązania do kompilacji. Nie wierzę, że jest to możliwe, jak stwierdzono w C++. Oto eksperyment myślowy:

pliku Bases.hpp:

class B1 {int val1;}; 
class B2 {int val2;}; 

pliku Derived.hpp:

#include <Bases.hpp> 
class D : public B1, public B2 {}; 

pliku Composite.hpp:

#include <Bases.hpp> 
class C 
{ 
    B1 b1; 
    B2 b2; 
}; 

pliku RandomReturn.cpp:

#include <Composite.hpp> 
#include <Derived.hpp> 
#include <cstdlib> 

static D derived; 
static C composite; 

void random_return(B1*& left, B2*& right) 
{ 
    if (std::rand() % 2 == 0) 
    { 
     left=static_cast<B1*>(&derived); 
     right=static_cast<B2*>(&derived); 
    } 
    else 
    { 
     left=&composite.b1; 
     right=&composite.b2; 
    } 
} 

Teraz załóżmy, że masz:

#include <Bases.hpp> 
#include <iostream> 

extern void random_return(B1*& , B2*&); 

// some conception of "is_same_object"  
template <...> bool is_same_object(...) ... 

int main() 
{ 
    B1 *left; 
    B2 *right; 

    random_return(left,right); 
    std::cout<<is_the_same_object(left,right)<<std::endl; 
} 

Jak moglibyśmy wdrożyć is_same_object tutaj, w czasie kompilacji, nie wiedząc nic o class C i class D?

Z drugiej strony, jeśli jesteś gotów do zmiany hipotezy, powinno być wykonalne:

class base_of_everything {}; 
class B1 : public virtual base_of_everything {}; 
class B2 : public virtual base_of_everything {}; 

class D : public B1, public B2, public virtual base_of_everything {}; 

... 
// check for same object 
D d; 
B1 *b1=static_cast<B1*>(&d); 
B2 *b2=static_cast<B2*>(&d); 

if (static_cast<base_of_everything*>(b1)==static_cast<base_of_everything*>(b2)) 
{ 
    ... 
} 
+0

Co to jest "wirtualna klasa"? ;) –

+0

oh, czy zrobiłem źle.Nie robię wirtualnego dziedziczenia zbyt wiele. – Managu

3

Jest łatwy i trudny sposób.

Łatwym sposobem jest wprowadzenie pustej wirtualnej klasy bazowej. Każdy obiekt dziedziczący z takiej klasy dostaje wskaźnik do wspólnego punktu w "rzeczywistym" obiekcie, który jest tym, czego chcesz. Wskaźnik ma trochę nad głową, ale nie ma gałęzi ani niczego.

class V {}; 
class B1 : public virtual V {}; // sizeof(B1) = sizeof(void*) 
class B2 : public virtual V {}; // sizeof(B2) = sizeof(void*) 
class D : public B1, public B2 {}; // sizeof(D) = 2*sizeof(void*) 

bool same(V const *l, V const *r) { return l == r; } 

Trudno jest spróbować użyć szablonów. W tej chwili jest już kilka hacków ... kiedy hakerzy z szablonami pamiętają, że jesteście w istocie odkrywczymi częściami języka, tylko z miejmy nadzieję, mniejszymi kosztami, zarządzając czasem kompilacji. Czy możemy obniżyć obciążenie wirtualnej klasy bazowej i wyeliminować ten wskaźnik? To zależy od tego, ile potrzebujesz ogólności. Jeśli twoje klasy bazowe można ułożyć na kilka różnych sposobów w obiekcie pochodnym, to z pewnością istnieją informacje, których nie można uzyskać w czasie kompilacji.

Ale jeśli twoja hierarchia dziedziczenia jest odwróconym drzewem (tzn. Budujesz duże obiekty przez wiele dziedziczenia) lub kilkoma takimi drzewami, możesz po prostu rzucić wskazówki do najbardziej pochodnych typów, takich jak to:

class C; // forward declare most derived type 
class T { public: typedef C base_t; }; // base class "knows" most derived type 
class B1: public T { int a; }; 
class B2: public T { int b; }; 
class D: public B1, public B2 { int c; }; 

// smart comparison function retrieves most-derived type 
// and performs simple addition to make base pointers comparable 
// (if that is not possible, it fails to compile) 
template< class ta, class tb > 
bool same(ta const *l, tb const *r) { 
     return static_cast< typename ta::base_t const * >(l) 
     == static_cast< typename tb::base_t const * >(r); 
} 

Oczywiście nie chcesz przekazywać NULL wskaźników do tej "zoptymalizowanej" wersji.

+1

Fajna wiadomość, ale nie odpowiedziałeś na jego pytanie. Cytat: "Nie chcę wywoływać żadnego operatora == przeciążenia i chcę, aby działał bez względu na to, jakie klasy bazowe, wirtualne klasy bazowe i wiele dziedziczenia są używane.". Rozumiem, że chce on mieć dowolne dwa wskaźniki do dowolnych dwóch typów losowych - których nie zna ani nie może modyfikować - i porównywać je pod kątem równości. –

+0

Najwyraźniej mogę tylko pokazać, co jest możliwe, i wyjaśnić, co nie jest ... Nie sądzę, żeby on naprawdę sprecyzował, że nie może modyfikować zajęć. Jego słowa "są tym samym obiektem", tak naprawdę nie mają zastosowania do jego pytania, ponieważ dwie klasy bazowe mogą być * w * tym samym obiekcie, ale będą miały całkowicie wyłączne interfejsy - a zatem będą całkowicie odmiennymi obiektami - chyba że będą dzielić wirtualną bazę. – Potatoswatter

+0

@Pavel: Właściwie to zinterpretowałem to jako: Nie chcę polegać na czymś poza moją kontrolą, tj. Na sposobie, w jaki kompilator używa przestrzeni adresowej. Sądzę, że nie ma prawie żadnych gwarancji w tym standardzie. – xtofl

0

Użyj boost::addressof. Myślę, że to najlepszy sposób. boost::addressof jest dostarczany, aby uzyskać adres mimo to, bez względu na potencjalne zastosowania i nadużycia przeciążenia operatora. Dzięki użyciu sprytnej maszyny wewnętrznej funkcja szablonu zapewnia dotarcie do rzeczywistego obiektu i jego adresu. Spójrz na tę

#include "boost/utility.hpp" 

class some_class {}; 

int main() { 
    some_class s; 
    some_class* p=boost::addressof(s); 
} 
+0

To nieistotne. Zaczyna się od domyślnej funkcjonalności "&" i chce ją ulepszyć, nie gwarantując, że nigdy się nie zmieni. – Potatoswatter

0

Jeśli chcesz porównać tożsamość swoich obiektów, dlaczego nie dałbyś im jednego? W końcu to jest ty, , który decyduje, co czyni tożsamość obiektu. Niech kompilator to zrobi, a ty jesteś ograniczony ograniczeniami kompilatora.

Coś na drodze ...

class identifiable { 
    public: 
    long long const /*or whatever type*/ identity; 
    static long long sf_lFreeId() { 
     static long long lFreeId = 0; 
     return lFreeId++; // not typesafe, yet 
    } 
    identifiable(): identity(sf_lFreeId()) {} 
    bool identical(const identifiable& other) const { 
     return identity == other. identity; 
    } 
}; 

class A : public identifiable { 
}; 

.... 

A a1, a2; 
A& a3 = a1; 

assert(!a1.identical(a2)); 
assert(a1.identical(a3)); 
+0

Nie ma powodu, aby umieszczać unikalny identyfikator wewnątrz obiektu, gdy wskaźnik jest już unikalną wartością, która nie zużywa pamięci - i potrzebujesz wskaźnika do pobrania identyfikatora. Powinieneś także użyć size_t dla takich ID: jest to poprawny rozmiar na wszystkich platformach i "long long" jest niestandardowy. – Potatoswatter

+0

@Potatoswatter: Obiekt może znajdować się na różnych komputerach (np. CORBA, JRI, COM + ...), więc jego tożsamość nie jest ograniczona ani zdefiniowana przez przestrzeń adresową, w której ją widzisz. Pytanie już stwierdza, że ​​ta wizja ma ograniczenia z dziedziczeniem wielokrotnym. Ma też innych. – xtofl

Powiązane problemy