2012-08-13 21 views
6

Así que tienen una interfaz de streaming virtual en C++Problemas con interfaz de flujo en C++

class KxStream 
{ 
public: 
    virtual KxStream& operator<< (u32 num) = 0; 
}; 

Tiene una tonelada de < básicos < operadores para todos los tipos incorporados. Acabo de enumerar uno.

Luego tengo algunas clases que implementan la interfaz de transmisión. De esta manera:

class KxCbuf : public KxStream 
{ 
public: 
    KxStream& operator<<(u32 num); 
} 

lo que es una implementación de la interfaz de streaming en KxCbuf. Hasta aquí todo bien. Entonces tengo algunas clases que sobrecargan la interfaz de tren:

class KxSymbol 
{ 
    operator u32() const; 
    friend KxStream& operator<<(KxStream& os, KxSymbol sym); 
}; 

Tenga en cuenta que esta clase ha echado a un operador de tipo incorporado. Ahora en que intento transmitir a una de estas clases en una de las clases que implementan la interfaz de streaming consigo un error:

KxCbuf buf; 
KxSymbol sym; 

buf << sym; // error! 

El compilador se confunde sobre qué funciones utilizar. Gcc compila bien, pero dice que hay varias maneras de hacer esto. MSVC no compila diciendo que hay múltiples sobrecargas:

src/variable.cpp(524) : error C2666: 'KxCbuf::operator <<' : 15 overloads have similar conversions 

sé lo que está pasando, pero no la forma de resolver de manera satisfactoria. Entonces el compilador puede lanzar KxCbuf -> KxStream, y luego llamar a la función de amigo, que es lo correcto. O puede emitir KxSymbol -> u32, y luego llamar al operador u32 < < en KxCbuf, heredado de KxStream.

Lo puedo resolver de dos (malas) maneras.

puedo empezar por el streaming en algo ambigua:

buf << "" << sym; 

De esta manera el valor de retorno del primer operador corriente de "" retorno KxStream, y todo está bien. O puedo implementar un operador de flujo redundante para la clase de implementación. P.ej. Puedo añadir lo siguiente a KxSymbol:

friend KxStream& operator<<(KxCbuf& os, KxSymbol sym); 

La primera respuesta siempre funciona - pero seguro que es feo. La segunda respuesta también es desagradable, ya que tengo que crear operadores de flujo redundantes, y no siempre funciona porque las implementaciones de KxStream no siempre son visibles en los lugares donde necesito definir nuevos operadores de flujo.

Idealmente me gustaría que las implementaciones de la interfaz KxStream funcionen igual que los objetos KxStream, y evite conversiones implícitas que causen conversiones ambiguas.

¿Cómo soluciono esto?

(ps tengo que crear mis propios operadores de transmisión para un esquema de serialización personalizado para mi biblioteca no puedo usar impulso o bibliotecas de terceros similares que tienen sus propias clases de serialización..)

@Edit: No hay varias buenas respuestas relacionadas con el control del uso de la conversión implícita del compilador, como la conversión a KxSymbol -> u32, desafortunadamente esa conversión implícita es importante para el código. Por ejemplo, KxSymbol es una clase que almacena cadenas en una tabla y las devuelve como números para que pueda comparar cadenas como números. P.ej. si dos símbolos no son iguales, entonces las cadenas no son lo mismo.También almaceno símbolos como números en algunas estructuras de datos.

¿Hay alguna manera de resolver esto desde el otro lado, de alguna manera hacer que el compilador entienda que las implementaciones de KxStream deben ser convertidas a objetos KxStream por preferencia a otras versiones implícitas?

Por ejemplo, ¿y si yo de alguna manera obligar al compilador a tener que echar primera KxCbuf a KxStream antes de usar el operador < < de la incorporada en los tipos. Esto haría que siempre prefiera el operador de sobrecarga < < sobre los KxStream. - las sobrecargas requerirían un lanzamiento, y las de KxStream requerirían dos.

+0

¿Qué sucede si pasa el 'KxSymbol sym' por const reference en lugar de copiar? Eso podría corregir el problema –

Respuesta

5

Si tiene un compilador C++ 11, marque la función de conversión "explícita". De esta forma, no obtendrás conversiones implícitas a u32, y este tipo de ambigüedad desaparecerá.

+0

Me gustaría evitar la necesidad de C++ 11. Este es un código multiplataforma que debe compilarse en lugares donde C++ 11 no siempre está disponible. –

+0

Luego debe deshacerse del operador de conversión, como sugirió Mark B. –

3

La forma más simple y segura es cambiar la conversión implícita de operator u32() const; a un método con nombre u32 as_u32() const. Esto no solo elimina la ambigüedad que puede evitar todo tipo de conversiones accidentales no deseadas que le causarán problemas en el futuro.

+0

Eso, por supuesto, tiene la ventaja de trabajar con compiladores que no han implementado "explícito" para los operadores de conversión. –

0

Creo que puedo tener una respuesta. Implemente los operadores de flujo integrados en métodos privados virtuales.

así que algo como esto:

class KxStream 
{ 
public: 
    KxStream& operator<< (u32 num) { return stream_i(num); } 
private: 
    virtual KxStream& stream_i (u32 num) = 0; 
}; 

class KxCbuf : public KxStream 
{ 
private: 
    KxStream& stream_i(u32 num); 
} 

Ahora no hay manera para que el compilador para llamar al operador incorporado < < través KxCbuf. Solo puede usar los métodos públicos en KxStream. Pero aún obtengo las sobrecargas de métodos virtuales a través de los métodos privados de stream_i. Por lo tanto, cuando se transmite a KxCbuf, el compilador siempre debe enviar a KxStream. Es la única forma.

Al decidir entre las sobrecargas de amigos y las incorporadas, el amigo sobrecarga gana porque las unidades integradas requieren 2 conversiones y las sobrecargas requieren una.