2012-03-25 9 views
14

que tienen una estructura que se define así:¿Por qué tengo que sobrecargar el operador == en los tipos de POD?

struct Vec3 { 
float x, y, z; 
} 

Cuando intenté usar std::unique en un std::vector<Vec3>, me encontré con este error:

Description Resource Path Location Type no match for ‘operator==’ in ‘_first._gnu_cxx::__normal_iterator<_Iterator, _Container>::operator* with _Iterator = Vec3*, _Container = std::vector > == _next._gnu_cxx::__normal_iterator<_Iterator, _Container>::operator* with _Iterator = Vec3*, _Container = std::vector >’ ModelConverter line 4351, external location: /usr/include/c++/4.4.6/bits/stl_algo.h C/C++ Problem

entiendo la necesidad de la naievite del compilador en en operadores de igualdad y otros (en este caso, * seguramente no sea lo que quiero decir), pero ¿es esto una cuestión de política, o hay una razón técnica para ello de la que no tengo conocimiento? ? Hay un operador de asignación predeterminado, entonces ¿por qué no hay un operador de igualdad predeterminado?

+1

Porque no existe un "comportamiento predeterminado" apropiado cuando se trata de la igualdad. –

+3

@EtiennedeMartel: Podría haber. En Ada, por ejemplo, la igualdad en los registros se define en términos de igualdad de los componentes. –

+1

La igualdad y la desigualdad son igualmente fáciles de definir. –

Respuesta

16

No hay ninguna razón técnica. Pedagógicamente, podría decirse que esto se debe a que C no le permite comparar dos estructuras con ==, y esta es una buena razón; ese cambio de comportamiento cuando vas a C++ no es obvio. (Es de suponer que la razón de que C no admite que es que la comparación de campo inteligente podría funcionar para algunos estructuras, pero definitivamente no es todo.)

Y sólo desde un C punto de vista ++, lo que si tiene una campo privado? Un valor predeterminado == expone técnicamente ese campo (indirectamente, pero aún). Entonces, ¿el compilador solo generaría un operator== si no hay miembros de datos privados o protegidos?

Además, hay clases que no tienen una definición razonable de igualdad (clases vacías, clases que no modelan el estado pero lo almacenan en caché, etc.) o para quienes la comprobación de igualdad predeterminada puede ser extremadamente confusa (clases que envuelven indicadores)

Y luego está la herencia. Decidir qué hacer para operator== en una situación de herencia es complicado, y sería fácil para el compilador tomar una decisión equivocada. (Por ejemplo, si esto fuera lo que hacía C++, probablemente obtendríamos preguntas sobre por qué == siempre tiene éxito cuando prueba la igualdad entre dos objetos que son ambos descendientes de una clase base abstracta y que se usan con una referencia).

Básicamente, se trata de un problema espinoso, y es más seguro para el compilador mantenerse al margen, incluso teniendo en cuenta que puede anular lo que decida el compilador.

+0

Gracias, no estaba al tanto de eso aspecto de C, y que podría haber clases que aparecen como simples contenedores agregados, pero no tienen un concepto de igualdad es bueno. –

2

¿Cuál le gustaría que fuera la operación de igualdad? Todos los campos son iguales? No va a tomar esa decisión por ti.

+0

En esta circunstancia, el 'operador ==' que definí hizo exactamente eso. Puedo ver dónde podría querer complejidad adicional en su comparador, pero no puedo pensar en una circunstancia en la que dos 'struct's con los mismos datos exactos sean desiguales. ¿Puedes pensar en un ejemplo? –

+0

Es la misma pregunta que "¿cuál sería el operador de asignación o constructor de copia predeterminado?" –

+2

KG: un ejemplo sería donde dos objetos son iguales si tienen la misma identidad (= dirección) –

4

La pregunta de por qué tiene que proporcionar operator== no es lo mismo que la pregunta de por qué debe proporcionar alguna función de comparación.

Con respecto a esto último, la razón por la que se le solicita que proporcione la lógica de comparación, es que la igualdad de elemento raras veces es apropiada. Considere, por ejemplo, una estructura POD con una matriz de char allí. Si se usa ’ s para contener una cadena terminada en cero, entonces dos de estas estructuras pueden comparar desigual en el nivel binario (debido a los contenidos arbitrarios después de los cero bytes en las cadenas) siendo lógicamente equivalente.

Además, aquí están todas las complicaciones de nivel C++ mencionadas por otras respuestas, p. Ej. el especialmente espinoso de la igualdad polimórfica (realmente no se necesita ’ ¡quiero que el compilador elija!).

Por lo tanto, básicamente, simplemente no hay una buena opción predeterminada, por lo que la elección es suya.

En relación con la primera pregunta, que es lo que, literalmente, preguntó, ¿por qué tiene que proporcionar operator==?

Si define operator< y operator==, las definiciones de operador en el espacio de nombres std::rel_ops pueden completar el resto por usted. Presumiblemente, la razón por la que se necesita operator== es que sería innecesariamente ineficaz implementarla en términos de operator< (luego requerir dos comparaciones). Sin embargo, la elección de estos dos operadores como base es completamente desconcertante, porque hace que el código de usuario sea detallado y complicado, y en algunos casos, ¡mucho menos eficiente que el posible!

En mi opinión, la mejor base para los operadores de comparación es la función de tres valores compare, como std::string::compare.

Dada una variante de función miembro comparedTo, a continuación, puede utilizar una clase de patrón de plantilla Curiosamente recurrente como la de abajo, para proporcionar el conjunto completo de operadores:

template< class Derived > 
class ComparisionOps 
{ 
public: 
    friend int compare(Derived const a, Derived const& b) 
    { 
     return a.comparedTo(b); 
    } 

    friend bool operator<(Derived const a, Derived const b) 
    { 
     return (compare(a, b) < 0); 
    } 

    friend bool operator<=(Derived const a, Derived const b) 
    { 
     return (compare(a, b) <= 0); 
    } 

    friend bool operator==(Derived const a, Derived const b) 
    { 
     return (compare(a, b) == 0); 
    } 

    friend bool operator>=(Derived const a, Derived const b) 
    { 
     return (compare(a, b) >= 0); 
    } 

    friend bool operator>(Derived const a, Derived const b) 
    { 
     return (compare(a, b) > 0); 
    } 

    friend bool operator!=(Derived const a, Derived const b) 
    { 
     return (compare(a, b) != 0); 
    } 
}; 

donde compare es una función sobrecargada, por ejemplo, de esta manera:

template< class Type > 
inline bool lt(Type const& a, Type const& b) 
{ 
    return std::less<Type>()(a, b); 
} 

template< class Type > 
inline bool eq(Type const& a, Type const& b) 
{ 
    return std::equal_to<Type>()(a, b); 
} 

template< class Type > 
inline int compare(Type const& a, Type const b) 
{ 
    return (lt(a, b)? -1 : eq(a, b)? 0 : +1); 
} 

template< class Char > 
inline int compare(basic_string<Char> const& a, basic_string<Char> const& b) 
{ 
    return a.compare(b); 
} 

template< class Char > 
inline int compareCStrings(Char const a[], Char const b[]) 
{ 
    typedef char_traits<Char> Traits; 

    Size const aLen = Traits::length(a); 
    Size const bLen = Traits::length(b); 

    // Since there can be negative Char values, cannot rely on comparision stopping 
    // at zero termination (this can probably be much optimized at assembly level): 
    int const way = Traits::compare(a, b, min(aLen, bLen)); 
    return (way == 0? compare(aLen, bLen) : way); 
} 

inline int compare(char const a[], char const b[]) 
{ 
    return compareCStrings(a, b); 
} 

inline int compare(wchar_t const a[], wchar_t const b[]) 
{ 
    return compareCStrings(a, b); 
} 

Ahora, que ’ s la maquinaria. ¿Qué aspecto tiene aplicarlo a su clase & hellip;

struct Vec3 
{ 
    float x, y, z; 
}; 

?

bien que ’ s bastante simple:

struct Vec3 
    : public ComparisionOps<Vec3> 
{ 
    float x, y, z; 

    int comparedTo(Vec3 const& other) const 
    { 
     if(int c = compare(x, other.x)) { return c; } 
     if(int c = compare(y, other.y)) { return c; } 
     if(int c = compare(z, other.z)) { return c; } 
     return 0; // Equal. 
    } 
}; 

de responsabilidad: Código y hellip no muy probado; :-)

+0

Esa fue una gran respuesta. El CRTP es algo que me gustaría saber más, gracias por mostrarlo en contexto! Tampoco había oído hablar de 'std :: rel_ops', así que tendré que analizarlo. –

Cuestiones relacionadas