2009-10-01 9 views
5

Tengo dos punteros a los objetos y quiero probar si son exactamente el mismo objeto de la manera más robusta. No deseo invocar explícitamente ninguna sobrecarga de operator == y quiero que funcione sin importar las clases base, las clases base virtuales y la herencia múltiple.¿Qué se debe usar para verificar la identidad en C++?

Mi código actual es la siguiente:

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

Y para mi caso esto funciona. Sin embargo, esto no funciona para este caso:

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

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

Subbing en reinterpert_cast, static_cast o dynamic_cast no funciona bien.


Particularmente estoy esperando algo que termine realmente simple y eficiente. Idealmente, no requeriría ninguna instrucción de bifurcación para implementar y haría algo así como, ajustar el puntero al inicio del objeto y comparar.

+0

El segundo caso no funciona porque los punteros son físicamente diferentes direcciones, cada una apuntando a un desplazamiento dentro del objeto derivado diferente clase base. Esto varía de compilador a compilador. –

Respuesta

8

Si las clases son realmente exactamente como se da entonces es imposible, ya que no hay suficiente información disponible en tiempo de ejecución para reconstruir la información requerida.

Si en realidad son clases polimórficas, con funciones virtuales, suena como dynamic_cast<void *> es la respuesta. Devuelve un puntero al objeto más derivado. Su cheque sería entonces dynamic_cast<void *>(a)==dynamic_cast<void *>(b).

Véase el párrafo 7 aquí:

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

sospecho aplican los habituales problemas dynamic_cast - es decir, no hay garantía de que será rápido, y sus clases tendrá que ser polimórficos.

Esta no es una característica que haya usado yo mismo, me temo, pero he visto que muchas personas lo han sugerido con suficiente frecuencia, infiero que es ampliamente compatible y funciona como se anuncia.

+0

Sí, todavía requeriría que la clase sea polimórfica (es decir, tenga vtable). –

+0

Pero ese es el precio a pagar, diría yo. Y de hecho, si realmente tiene herencia múltiple así, parece que tiene clases polimórficas de todos modos. Si no, un simple 'virtual ~ B1() {}' y el mismo para 'B2' estarán bien. +1 de hecho - maldición vi la barra roja de peligro mientras escribía algo así como esta respuesta xD –

+0

Aceptaré la no solución en el caso no virtual y este parece ser el mejor en el caso virtual. – BCS

1

A excepción de los punteros inteligentes (que no son realmente punteros, sino objetos de clase), la sobrecarga operator== no es posible para los punteros, por lo que no debería ser necesario un molde.

Por supuesto, la comparación de punteros de diferentes tipos podría no funcionar. ¿Por qué crees que necesitas hacer eso?

+0

Supongo que están tratando de comparar dos punteros de interfaz para ver si apuntan al mismo objeto. –

+0

Estoy buscando una expresión repetitiva que pueda usar en cualquier situación. – BCS

-2

Puede marcar para señalar si los objetos señalados se superponen en la memoria (robo de la respuesta de Mark Ransom). Esto invoca un comportamiento indefinido, pero debe hacer lo que desee en cualquier compilador razonable:

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

No creo que funcione si las referencias son a clases base independientes, ya que (creo) serán adyacentes en lugar de superponerse – BCS

+0

Esto me devuelve falso en Visual Studio 2005 por el ejemplo dado. Los valores son: left_begin = 1245027; left_end = 1245028; right_begin = 1245028; right_end = 1245029 – garethm

+1

Esto también podría tener problemas con la herencia múltiple. – Managu

-1

Su enfoque funcionó para mí, incluso en el caso citado:

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

} 

impresiones "igual" aquí ..

+0

Necesita funciones virtuales en B1 y B2 para ver la diferencia. –

+0

¿en qué estás corriendo? Lo entiendo en GCC pero falla bajo DMC (de digitalmars) – BCS

+1

g ++ hizo vacío la optimización de la clase base allí, al parecer. Solo pega un campo 'char' tanto en 'B1' como en' B2'. –

3

No hay una manera general de hacerlo. Los subobjetos de clase base, en general, no tienen conocimiento de que son eso, por lo que si solo tiene un puntero a un subobjeto de clase base, no tiene ningún medio para obtener un puntero al objeto más derivado al que pertenece, si no sé el tipo de este último de antemano.

Vamos a empezar con esto:

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

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

Considérese una implementación típica de diseño en memoria de D. En ausencia de vtable, lo único que tenemos es los datos en bruto (y posiblemente de relleno):

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

Usted tiene dos punteros:

B1* p1; 
B2* p2; 

Cada puntero apunta de manera efectiva en un solo char dentro de una instancia de D. Sin embargo, si no sabes eso de antemano, ¿cómo puedes saberlo? También existe la posibilidad de que los punteros puedan apuntar a subobjetos dentro de una instancia de Z, y al observar los valores de los punteros en sí mismos, claramente no hay forma de saberlo; ni hay nada que usted (o el compilador) pueda deducir de los datos a los que hace referencia el puntero, ya que es solo un byte único de datos en la estructura.

+0

Diría que, en esencia, el problema aquí es que en C++ '(B1 *) c' y' (B2 *) c' no son el mismo objeto, en la medida en que esos tipos lo saben, pero el que pregunta quiere define "mismo objeto" para significar que son (porque la clase C sabe que lo son). Supongo que podría definir una función virtual GetMostDerivedPtr en todas sus interfaces, y hacer que cada clase lo implemente (quizás usando un ayudante CRTP) para devolver '(void *) this' (' (void *) static_cast (this) 'con el ayudante). –

+0

Bueno, son subobjetos de clase base del mismo objeto más derivado, y el significado de eso está bien definido en ISO C++ ... simplemente no es algo que se pueda hacer dentro de las restricciones dadas. –

+0

El compilador sabe de antemano a qué subobjeto va cada apuntador, suponiendo que son subobjetos. Si lanzas ambos punteros a una D, saldrán iguales. El único problema es introducir algún tipo al cual ambos puedan ser lanzados. Esto se puede hacer ya sea con una base virtual o metaprogramación de plantillas. – Potatoswatter

0

Por lo tanto, está buscando una solución de tiempo de compilación. No creo que esto sea posible, como se indica en C++. He aquí un experimento mental:

Archivo Bases.hpp:

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

Archivo Derived.hpp:

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

Archivo Composite.hpp:

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

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

Ahora, supongamos que tiene:

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

¿Cómo es posible aplicar is_same_object aquí, en tiempo de compilación, sin saber nada de class C y class D?

Por otro lado, si usted está dispuesto a cambiar las hipótesis, debe ser viable:

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

¿Qué es 'clase virtual'? ;) –

+0

oh, me equivoqué.No hago mucho herencia virtual. – Managu

3

Hay una manera fácil y una manera difícil.

La manera fácil es introducir una clase base virtual vacía. Cada objeto que hereda de dicha clase obtiene un puntero al punto común en el objeto "real", que es lo que desea. El puntero tiene un poco por encima, pero no hay ramas ni nada.

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

La manera más difícil es tratar de usar plantillas. Aquí ya hay algunos hacks ... cuando hackeo con plantillas, recuerde que básicamente está reinventando parte del lenguaje, solo con una sobrecarga con suerte administrando la información del tiempo de compilación. ¿Podemos reducir la sobrecarga de la clase base virtual y eliminar ese puntero? Depende de cuánta generalidad necesites. Si sus clases base se pueden organizar de diferentes maneras dentro de un objeto derivado, entonces ciertamente hay información que no puede obtener en tiempo de compilación.

Pero si su jerarquía de herencia es un árbol al revés (es decir, está construyendo objetos grandes por lotes de herencia múltiple), o varios de tales árboles, puede seguir adelante y convertir los punteros al tipo más derivado como esto:

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

Por supuesto, no desea pasar punteros NULL a esta versión "optimizada".

+1

Buen informe, pero no respondió su pregunta. Cita: "explícitamente no quiero invocar ninguna sobrecarga de operador == y quiero que funcione sin importar qué clases base, clases base virtuales y herencia múltiple se usen". Supongo que quiere decir que quiere poder tomar dos punteros a dos tipos aleatorios, que él no conoce ni puede modificar, y compararlos por igualdad. –

+0

Supongo que lo máximo que puedo hacer es mostrar lo que es posible y explicar lo que no ... No creo que haya especificado que no puede modificar las clases. Sus palabras "son el mismo objeto" no se aplican realmente a su pregunta, ya que dos clases base pueden ser * en * el mismo objeto pero tendrán interfaces totalmente exclusivas y, por lo tanto, serán objetos totalmente diferentes, a menos que compartan una base virtual. – Potatoswatter

+0

@Pavel: De hecho, interpreté esto como: no quiero depender de algo que esté fuera de mi control, es decir, la forma en que el compilador utiliza el espacio de direcciones. Apenas hay garantías sobre eso en el estándar, supongo. – xtofl

0

Use boost::addressof. Estoy pensando que esta es la mejor manera. boost::addressof se proporciona para obtener la dirección de todos modos, independientemente de los posibles usos y mal uso de la sobrecarga del operador. Mediante el uso de alguna maquinaria interna inteligente, la función de plantilla addressof garantiza que llegue al objeto real y a su dirección. Mira esto

#include "boost/utility.hpp" 

class some_class {}; 

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

Eso es irrelevante. Está comenzando con la funcionalidad predeterminada de "&" y desea mejorarla, no para garantizar que nunca cambie. – Potatoswatter

0

Si necesitas comparar la identidad de tus objetos, ¿por qué no les das uno? Después de todo, es usted quien decide qué hace la identidad del objeto. Deje que el compilador lo haga y estará sujeto a las limitaciones del compilador.

Algo en el camino de ...

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

No hay ninguna razón para poner una ID única dentro de un objeto cuando un puntero ya es un valor único que no consume memoria, y necesita el puntero para recuperar la ID. Además, debe usar size_t para tales ID: tiene el tamaño correcto en todas las plataformas y "long long" no es estándar. – Potatoswatter

+0

@Potatoswatter: Un objeto puede residir en diferentes computadoras (es decir, CORBA, JRI, COM + ...), por lo que su identidad no está limitada ni definida por el espacio de direcciones en que lo ve. La pregunta ya indica que esta visión tiene limitaciones con herencia múltiple Tiene otros, también. – xtofl

Cuestiones relacionadas