2009-10-01 19 views
5

Ho due puntatori agli oggetti e voglio verificare se sono esattamente lo stesso oggetto nel modo più affidabile. Non desidero esplicitamente richiamare alcun sovraccarico di operator == e voglio che funzioni, a prescindere dalle classi di base, dalle classi di base virtuali e dall'ereditarietà multipla.Che cosa dovrebbe essere usato per verificare l'identità in C++?

mio codice attuale è questo:

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

E per il mio caso questo funziona. Tuttavia, questo non funziona per questo caso:

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

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

sottostrato in reinterpert_cast, static_cast o dynamic_cast non funziona neanche.


In particolare, spero in qualcosa che risulti davvero semplice ed efficiente. Idealmente non richiederebbe istruzioni di ramificazione da implementare e farebbe qualcosa di simile, aggiustando il puntatore all'inizio dell'oggetto e confrontandolo.

+0

Il secondo caso non funziona perché i puntatori sono fisicamente diversi indirizzi, ognuno punta a compensare all'interno dell'oggetto derivato una diversa classe di base. Questo varia dal compilatore al compilatore. –

risposta

8

Se le classi sono veramente esattamente come dato allora è impossibile in quanto non c'è abbastanza informazioni disponibili in fase di esecuzione ricostruire le informazioni richieste.

Se sono effettivamente classi polimorfiche, con funzioni virtuali, sembra che dynamic_cast<void *> sia la risposta. Restituisce un puntatore all'oggetto più derivato. L'assegno sarebbe quindi dynamic_cast<void *>(a)==dynamic_cast<void *>(b).

Si veda il paragrafo 7 qui:

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

ho il sospetto che si applicano i soliti problemi dynamic_cast - vale a dire, alcuna garanzia che sarà veloce, e le vostre classi dovranno essere polimorfico.

Questa non è una funzionalità che mi sono abituato, temo - ma l'ho visto suggerito abbastanza spesso da persone che hanno dedotto che è ampiamente supportato e funziona come pubblicizzato.

+0

Sì, richiederebbe comunque che la classe sia polimorfica (vale a dire vtable). –

+0

Ma questo è il prezzo da pagare, direi. E in effetti, se ha davvero un'eredità multipla del genere, sembra che abbia classi polimorfiche comunque. In caso contrario, un semplice 'virtuale ~ B1() {}' e lo stesso per 'B2' andrà tutto bene. +1 anzi - dannazione ho visto la barra rossa del pericolo mentre scrivevo qualcosa come questa risposta xD –

+0

Accetterò la soluzione no nel caso non virtuale e questo sembra essere il migliore nel caso virtuale. – BCS

1

Fatta eccezione per i puntatori intelligenti (che non sono in realtà puntatori, ma oggetti di classe), sovraccarico operator== non è possibile per i puntatori, in modo da un cast non dovrebbe essere necessario.

Ovviamente, il confronto di puntatori di tipi diversi potrebbe non funzionare. Perché pensi di aver bisogno di farlo?

+0

Sto cercando di confrontare due puntatori di interfaccia per vedere se puntano allo stesso oggetto. –

+0

Sto cercando un'espressione che possa essere utilizzata in qualsiasi situazione. – BCS

-2

È possibile controllare se gli oggetti puntati si sovrappongono in memoria (rubando dalla risposta di Mark Ransom). Questo richiama comportamento non definito, ma dovrebbe fare quello che vuoi in qualsiasi compilatore ragionevole:

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

Non penso che funzionerebbe se i riferimenti fossero a classi di base indipendenti come sarebbero (penso) essere adiacenti anziché sovrapposti – BCS

+0

Questo restituisce false per me in Visual Studio 2005 per l'esempio fornito. I valori sono: left_begin = 1245027; left_end = 1245028; right_begin = 1245028; right_end = 1245029 – garethm

+1

Questo potrebbe anche avere problemi con ereditarietà multipla. – Managu

-1

vostro approccio ha funzionato per me anche nel tuo caso citato:

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!"); 
} 

} 

stampe "uguale" qui ..

+0

Per visualizzare la differenza sono necessarie funzioni virtuali in B1 e B2. –

+0

su cosa stai lavorando? Lo prendo su GCC ma fallisce sotto DMC (da digitalmars) – BCS

+1

g ++ ha fatto l'ottimizzazione della classe base vuota lì, a quanto pare. Basta inserire un campo 'char' in' B1' e 'B2'. –

3

Non c'è un modo generale per farlo. In generale i sottooggetti di classe base, in generale, non sanno che sono così, quindi se si ha solo un puntatore a un sottooggetto di classe base, non si ha alcun mezzo per ottenere un puntatore all'oggetto più derivato a cui appartiene, se si non conosco il tipo di quest'ultimo in anticipo.

Partiamo da questo:

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

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

consideri una tipica implementazione di layout in-memory di D. In assenza di vtable, l'unica cosa che abbiamo è i dati grezzi (e possibilmente padding):

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

Hai due puntatori:

B1* p1; 
B2* p2; 

Ogni puntatore punta in modo efficace in un unico char all'interno di un istanza di D. Tuttavia, se non lo sai in anticipo, come potresti dire? C'è anche la possibilità che i puntatori possano puntare piuttosto ai sottooggetti all'interno di un'istanza di Z, e guardando i valori dei puntatori stessi, non c'è chiaramente modo di dirlo; né c'è nulla che tu (o il compilatore) possa dedurre dai dati a cui fa riferimento il puntatore, dato che è solo un singolo byte di dati nella struct.

+0

Direi che in sostanza, il problema qui è che in C++ '(B1 *) c' e' (B2 *) c' non sono lo stesso oggetto, per quanto ne sappiamo, ma l'interrogante vuole definisci "stesso oggetto" per indicare che lo sono (perché la classe C sa che lo sono). Immagino che tu possa definire una funzione virtuale GetMostDerivedPtr in tutte le tue interfacce, e che ogni classe la implementa (magari usando un helper CRTP) per restituire '(void *) this' (' (void *) static_cast (this) 'con il aiutante). –

+0

Bene, sono sottooggetti di classe base dello stesso oggetto più derivato, e il significato di ciò è ben definito in ISO C++ ... non è qualcosa che può essere fatto all'interno dei vincoli dati. –

+0

Il compilatore sa in anticipo subobject su cui ogni puntatore va, supponendo che siano subobjects. Se lanci entrambi i puntatori a una D, verranno uguali. L'unico problema è l'introduzione di un tipo in cui entrambi possono essere lanciati. Questo può essere fatto con una base virtuale o una metaprogrammazione del modello. – Potatoswatter

0

Quindi, stai cercando una soluzione di tempo di compilazione. Non credo che questo sia possibile come affermato in C++. Ecco un esperimento mentale:

File Bases.hpp:

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

File Derived.hpp:

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

File Composite.hpp:

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

File 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; 
    } 
} 

Ora, supponiamo di avere:

#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; 
} 

come potremmo implementare is_same_object qui, al momento della compilazione, senza sapere nulla di class C e class D?

D'altra parte, se siete disposti a cambiare le ipotesi, dovrebbe essere praticabile:

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

Cos'è la "classe virtuale"? ;) –

+0

oh, ho sbagliato.Non faccio molto l'ereditarietà virtuale. – Managu

3

C'è un modo semplice e difficile.

Il modo semplice è di introdurre una classe di base virtuale vuota. Ogni oggetto che eredita da una classe di questo tipo ottiene un puntatore al punto comune nell'oggetto "reale", che è ciò che si desidera. Il puntatore ha un piccolo overhead ma non ci sono rami o altro.

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; } 

Il modo difficile è provare a utilizzare i modelli. Ci sono già alcuni hack qui ... quando l'hacking con i modelli ricorda che stai essenzialmente reinventando parte del linguaggio, solo con un sovraccarico sperabilmente inferiore gestendo le informazioni sul tempo di compilazione. Possiamo ridurre il sovraccarico della classe di base virtuale ed eliminare quel puntatore? Dipende da quanta generalità hai bisogno. Se le tue classi base possono essere organizzate in diversi modi all'interno di un oggetto derivato, allora ci sono certamente informazioni che non puoi ottenere in fase di compilazione.

Ma se la gerarchia di ereditarietà è un albero capovolto (ovvero, si stanno costruendo oggetti di grandi dimensioni con un'elevata ereditarietà multipla) o più di tali alberi, è possibile andare avanti e lanciare i puntatori sul tipo più derivato come this:

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); 
} 

Ovviamente, non si desidera passare i puntatori NULL a questa versione "ottimizzata".

+1

Bel riassunto, ma non hai risposto alla sua domanda. Citazione: "Non desidero esplicitamente invocare alcun overload == dell'operatore e voglio che funzioni, a prescindere dalle classi di base, dalle classi di base virtuali e dall'ereditarietà multipla.". Intendo dire che vuole essere in grado di prendere due qualsiasi puntatori a due tipi casuali - che non sa né può modificare - e li ha confrontati per l'uguaglianza. –

+0

Immagino che il massimo che posso fare sia mostrare cosa è possibile e spiegare cosa non lo è ... Non penso che abbia ancora specificato che non può modificare le classi. Le sue parole "sono lo stesso oggetto" non si applicano realmente alla sua domanda, in quanto due classi base possono essere * nello * stesso oggetto ma avranno interfacce interamente esclusive - e quindi essere oggetti completamente diversi - a meno che non condividano una base virtuale. – Potatoswatter

+0

@Pavel: In effetti, l'ho interpretato come: Non voglio fare affidamento su qualcosa fuori dal mio controllo, cioè il modo in cui il compilatore utilizza lo spazio degli indirizzi. Non ci sono quasi garanzie su questo nello standard, immagino. – xtofl

0

Utilizzare boost::addressof. Sto pensando che questo sia il modo migliore. boost::addressof viene fornito per ottenere comunque l'indirizzo, indipendentemente dai potenziali usi e utilizzi impropri del sovraccarico dell'operatore. Usando un macchinario interno intelligente, la funzione template addressof assicura che arrivi all'oggetto reale e al suo indirizzo. Guarda questo

#include "boost/utility.hpp" 

class some_class {}; 

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

Questo è irrilevante. Sta iniziando con la funzionalità predefinita di "&" e vuole migliorarlo, non per garantire che non cambi mai. – Potatoswatter

0

Se hai bisogno di confrontare l'identità dei tuoi oggetti, perché non vuoi dargliene uno? Dopotutto, è che tu decidi che cosa rende l'identità dell'oggetto,. Lascia che sia il compilatore a farlo e sei vincolato alle limitazioni del compilatore.

Qualcosa nel modo di ...

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

Non c'è motivo di inserire un ID univoco all'interno di un oggetto quando un puntatore è già un valore univoco che non consuma memoria, e per il recupero dell'ID è necessario il puntatore. Inoltre, dovresti usare size_t per tali ID: è la dimensione corretta su tutte le piattaforme e "long long" non è standard. – Potatoswatter

+0

@Potatoswatter: un oggetto può risiedere su computer diversi (es. CORBA, JRI, COM + ...), quindi l'identità non è limitata, né definita dallo spazio degli indirizzi in cui la vedi. La domanda afferma già che questa visione ha dei limiti con eredità multipla. Ne ha anche altri. – xtofl

Problemi correlati