2009-03-02 29 views
5

Antecedentessobrecarga de operadores << - C++

Tengo una clase de contenedor que utiliza vectores < std :: string > internamente. He proporcionado un método AddChar (std :: string) a esta clase contenedora que hace un push_back() en el vector interno. En mi código, tengo que agregar varios elementos al contenedor en algún momento. Para eso tengo que usar

container.AddChar("First"); 
container.AddChar("Second"); 

Esto hace que el código sea más grande. Entonces, para hacerlo más fácil, planeo sobrecargar al operador < <. Para que pueda escribir

container << "First" << "Second" 

y dos elementos se agregarán al vector subyacente.

Este es el código que utilicé para que

class ExtendedVector 
{ 
private: 
    vector<string> container; 

public: 
    friend ExtendedVector& operator<<(ExtendedVector& cont,const std::string str){ 
     cont.AddChar(str); 
     return cont; 
    } 

    void AddChar(const std::string str) 
    { 
     container.push_back(str); 
    } 

    string ToString() 
    { 
     string output; 
     vector<string>::iterator it = container.begin(); 
     while(it != container.end()) 
     { 
      output += *it; 
      ++it; 
     } 
     return output; 
    } 
}; 

Está funcionando como se esperaba.

Preguntas

  1. es la sobrecarga del operador escriben de forma correcta?
  2. ¿Es una buena práctica sobrecargar a los operadores en situaciones como esta?
  3. ¿Habrá algún problema de rendimiento o algún otro problema con este código?

¿Alguna idea?

Editar

Después de escuchar las excelentes comentarios, decidí no sobrecargar < < ya que no tiene sentido aquí. Eliminé el código de sobrecarga del operador y aquí está el código final.

class ExtendedVector 
{ 
private: 
    vector<string> container; 

public: 

    ExtendedVector& AddChar(const std::string str) 
    { 
     container.push_back(str); 
     return *this; 
    } 

     .. other methods 
} 

Esto me permite agregar

container.AddChar("First").AddChar("Second") 

En C#, yo puedo hacer esto más fácilmente mediante el uso de la palabra clave params. Código será como

void AddChar(params string[] str) 
{ 
    foreach(string s in str) 
     // add to the underlying collection 
} 

Sé que en C++, podemos utilizar ... para especificar langth variable de parámetros. Pero AFAIK, no es seguro. Entonces, ¿es una práctica recomendada hacerlo? Para que pueda escribir

container.AddChar("First","Second") 

Gracias por las respuestas.

+0

Qt usa el operador << para QStringList: http://doc.trolltech.com/4.4/qstringlist.html, y me ha gustado su uso con ese. pero en general, me gustaría no agregar demasiados operadores. como dice Kevin, puede ser confuso como el infierno :) –

Respuesta

8

¿Se ha escrito la sobrecarga del operador correctamente?

Lo es, pero uno puede hacerlo mejor. Al igual que alguien más mencionado, su función puede definirse por completo a partir de funciones públicas existentes. ¿Por qué no hacer que use solo eso? En este momento, es un amigo, lo que significa que pertenece a los detalles de implementación. Lo mismo es cierto si coloca el operador < < como miembro en su clase. Sin embargo, haga que su operador < < sea un no miembro, no amigo función.

class ExtendedVector { 
    ... 
}; 

// note, now it is *entirely decoupled* from any private members! 
ExtendedVector& operator<<(ExtendedVector& cont, const std::string& str){ 
    cont.AddChar(str); 
    return cont; 
} 

Si cambia de clase, no estará seguro de que su operador que < < seguirá funcionando. Pero si su operador < < depende completamente de funciones públicas, entonces puede estar seguro de que funcionará después de que se hayan realizado cambios en los detalles de implementación de su clase solamente. ¡Hurra!

¿Es una buena práctica sobrecargar a los operadores en situaciones como esta?

Como dijo otro hombre de nuevo, esto es discutible. En muchas situaciones, la sobrecarga del operador se verá "ordenada" a primera vista, pero se verá como el infierno el próximo año, porque ya no tienes ni idea de lo que tenías en mente cuando dabas amor a algunos símbolos. En el caso del operador < <, creo que este es un buen uso. Su uso como operador de inserción para transmisiones es bien conocido. Y sé de aplicaciones Qt y KDE que lo utilizan ampliamente en casos como

QStringList items; 
items << "item1" << "item2"; 

Un caso similar es boost.format que también reutiliza operator% para pasar argumentos a favor de los marcadores de posición en su cadena:

format("hello %1%, i'm %2% y'old") % "benny" % 21 

Es, por supuesto, también discutible para usarlo allí. Pero su uso para el formato printf es bien conocido y su uso también está bien allí, en mi humilde opinión. Pero como siempre, el estilo también es subjetivo, así que tómalo con un grano de sal :)

¿Cómo puedo aceptar argumentos de longitud variable de forma segura?

Bueno, no es la manera de aceptar un vector si usted está buscando argumentos homogéneos:

void AddChars(std::vector<std::string> const& v) { 
    std::vector<std::string>::const_iterator cit = 
     v.begin(); 
    for(;cit != v.begin(); ++cit) { 
     AddChar(*cit); 
    } 
} 

En realidad no es confortable para pasarlo sin embargo. Tienes que construir tu vector manualmente y luego pasar ... Veo que ya tienes la sensación correcta sobre las funciones de estilo vararg. No se deben usar para este tipo de código y solo cuando se hace interfaz con el código C o funciones de depuración en todo caso. Otra forma de manejar este caso es aplicar la programación del preprocesador. Este es un tema avanzado y es bastante hacky. La idea es generar automáticamente las sobrecargas hasta un límite superior más o menos así:

#define GEN_OVERLOAD(X) \ 
void AddChars(GEN_ARGS(X, std::string arg)) { \ 
    /* now access arg0 ... arg(X-1) */ \ 
    /* AddChar(arg0); ... AddChar(arg(N-1)); */ \ 
    GEN_PRINT_ARG1(X, AddChar, arg) \ 
} 

/* call macro with 0, 1, ..., 9 as argument 
GEN_PRINT(10, GEN_OVERLOAD) 

Eso es pseudo código. Puede echar un vistazo a la biblioteca del preprocesador de refuerzo here.

La próxima versión de C++ ofrecerá mejores posibilidades. listas inicializador se pueden utilizar:

void AddChars(initializer_list<std::string> ilist) { 
    // range based for loop 
    for(std::string const& s : ilist) { 
     AddChar(s); 
    } 
} 

... 
AddChars({"hello", "you", "this is fun"}); 

También es posible en el próximo C++ para soportar muchos argumentos arbitrarias (de tipo mixto) utilizando variadic templates. GCC4.4 tendrá soporte para ellos. GCC 4.3 ya los admite parcialmente.

+0

Como siempre gran publicación. Gracias –

+0

me alegro de que te guste –

+0

"most" see 'container << elt1 << elt2' pero luego puedes usar [Eigen] (http://eigen.tuxfamily.org) que tiene' container << elt1, elt2'. Supongo que guarda un carácter que no sea de espacio en blanco por elemento, pero es un claro ejemplo de personas que no se apegan al status quo :) –

3

¿Es una buena práctica sobrecargar a los operadores en situaciones como esta?

Yo no lo creo.Es confuso como el infierno para alguien que no sabe que ha sobrecargado el operador. Solo adhiérase a los nombres de métodos descriptivos y olvídese de los caracteres adicionales que está escribiendo, simplemente no vale la pena. Su mantenedor (o usted mismo en 6 meses) se lo agradecerá.

+0

Estoy de acuerdo. La sobrecarga del operador puede ser genial, pero cuando intentas mantener el código meses después, incluso si lo escribiste, quieres ser capaz de entender lo que hace con un mínimo esfuerzo. Borrar el código es mejor que el código genial que es confuso. –

+0

Estoy de acuerdo. Siempre y cuando no cambie el SIGNIFICADO del operador. ES DECIR. MyWebRequest = "url"; no debe REALMENTE realizar la solicitud web. Para cosas como esta, prefiero el encadenamiento de funciones o la función de tomar una matriz. – Rahly

3

1) Sí, salvo que AddChar es público, no hay ninguna razón por la que deba ser un friend.

2) Esto es discutible. << se encuentra en la posición de ser el operador cuya sobrecarga para cosas "raras" se acepta al menos de mala gana.

3) Nada obvio. Como siempre, perfilar es tu amigo. Es posible que desee considerar pasar los parámetros de cadena a AddChar y operator<< por const reference (const std::string&) para evitar copias innecesarias.

+0

+1 para la referencia std :: string para evitar copiar. Y para todo lo demás. – Frank

+0

Gracias. Pero si elimino amigo, el compilador arrojará un error. –

+0

de hecho, te di un +1 también. me gusta especialmente el # 1. Sin embargo, lo dejaré en claro: "conviértalo en una función para no amigos que no sean miembros" :) y también me gusta el # 2. en lugar de decir "mal malo", das una base para los argumentos :) y creo que op << está bien aceptado como una inserción-op. –

2

preferiría no cargar de esa manera personalmente porque vectores normalmente no tienen un operador de desplazamiento a la izquierda sobrecargado - en realidad no es el mismo de la expresión idiomática ;-)

probablemente me vuelva una referencia de AddChar vez de este modo:

ExtendedVector& AddChar(const std::string& str) { 
    container.push_back(str); 
    return *this; 
} 

modo que lo pueda hacer

container.AddChar("First").AddChar("Second"); 

que no es realmente mucho más grande que los operadores BitShift.

(ver también el comentario de Logan sobre pasar cadenas por referencia en lugar de por valor).

+0

¡Me ganaste a esto! Por, uhm, unos minutos ...;) – Frank

+0

Esa es una buena sugerencia. Gracias. –

+0

Aunque no es un vector. Es una clase que sucede que almacena un vector internamente. operador << podría tener perfecto sentido. Después de todo, ya es estándar para clases similares a stream. Sin embargo, necesitaría un poco más de contexto para decirlo con seguridad. :) – jalf

1

El operador no está sobrecargado correctamente aquí. No hay ninguna razón para hacer al operador un amigo ya que puede ser un miembro de la clase. Friend es para funciones que no son miembros reales de la clase (como cuando se sobrecarga < < para ostream, por lo que el objeto se puede enviar a cout o ofstreams).

lo que realmente quiere que el operador sea:

ExtendedVector& operator<<(const std::string str){ 
    AddChar(str); 
    return *this; 
} 

lo general se considera una mala práctica a la sobrecarga de los operadores de una manera que les hace hacer algo que lo hacen normalmente. < < es normalmente un cambio de bit, por lo que sobrecargarlo de esta manera puede ser confuso. Obviamente, STL sobrecarga < < para la "inserción de secuencias" y, por lo tanto, junto con eso podría tener sentido sobrecargarlo para su uso de una manera similar. Pero eso no parece lo que estás haciendo, por lo que probablemente quieras evitarlo.

No hay problemas de rendimiento ya que la sobrecarga del operador es la misma que la de una llamada a función normal, solo la llamada se oculta porque el compilador la realiza automáticamente.

2

La sobrecarga del operador en este caso no es una buena práctica ya que hace que el código sea menos legible. El estándar std::vector no lo tiene para empujar elementos, por buenas razones.

Si usted está preocupado por el código de llamada es demasiado largo, usted podría considerar esto en vez del operador sobrecargado:

container.AddChar("First").AddChar("Second"); 

Esto será posible si tiene AddChar() retorno *this.

Es curioso que tenga esta función toString(). En el ese caso, un operator<< para enviar a una transmisión sería lo estándar en su lugar. Por lo tanto, si desea utilizar operadores, haga que la función toString() sea operator<<.

+0

Buena sugerencia. Gracias –

+1

Esto es al menos tan confuso e inesperado como la << semántica –

0

Esto va a hacer que las cosas bastante confusa, me gustaría utilizar la misma sintaxis que std :: cin en una variable:

std::cin >> someint;

"First" >> container;

De esta manera es al menos una inserción operador. Para mí, cuando algo tiene un operador sobrecargado < <, espero que esté generando algo. Al igual que std :: cout.

+0

Hmm. ¡Pienso en >> como extracción * de * la corriente de entrada a mis objetos! –

+0

No creo que haya una respuesta correcta, no creo que el operador << nor the >> tenga razón para lo que está intentando lograr. Personalmente, simplemente mantendría el mecanismo de push_back que ya está establecido como la forma "correcta" de hacer las cosas, el tipeo extra no es algo malo. –

Cuestiones relacionadas