Pregunta

Background

Tengo una clase contenedora que usa vector < std :: string > internamente. He proporcionado un método AddChar (std :: string) a esta clase de envoltura que hace un push_back () al vector interno. En mi código, tengo que agregar varios elementos al contenedor alguna vez. Para eso tengo que usar

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

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

container << "First" << "Second"

y dos elementos se agregarán al vector subyacente.

Aquí está el código que usé para eso

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. ¿La sobrecarga del operador está escrita correctamente?
  2. ¿Es una buena práctica sobrecargar a los operadores en situaciones como esta?
  3. ¿Habrá algún problema de rendimiento o cualquier otro problema con este código?

¿Algún pensamiento?

Editar

Después de escuchar los excelentes comentarios, decidí no sobrecargar < < ya que no tiene sentido aquí. Quité 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 #, puedo hacer esto más fácilmente usando la palabra clave params. El código será como

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

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

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

Gracias por las respuestas.

¿Fue útil?

Solución

  

¿La sobrecarga del operador está escrita correctamente?

Lo es, pero uno puede hacerlo mejor. Al igual que otra persona mencionada, su función puede definirse completamente a partir de funciones públicas existentes. ¿Por qué no hacerlo solo? En este momento, es un amigo, lo que significa que pertenece a los detalles de la implementación. Lo mismo es cierto si coloca el operador < < como miembro en su clase. Sin embargo, haga que su operador < < una función no miembro , no amiga .

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 su clase, no estará seguro de que su operador < < todavía funcionará Pero si su operador < < depende únicamente de las funciones públicas, entonces puede estar seguro de que funcionará después de que se hayan realizado cambios en los detalles de la implementación de su clase solamente. Yay!

  

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

Como dijo otro chico otra vez, esto es discutible. En muchas situaciones, la sobrecarga del operador se verá " limpia " 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 al darles a algunos símbolos un amor especial. En el caso del operador < < ;, creo que este es un uso correcto. Su uso como operador de inserción para flujos 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 para marcadores de posición en su cadena:

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

Por supuesto, también es discutible usarlo allí. Pero su uso para las especificaciones del formato printf son bien conocidos y, por lo tanto, su uso también está bien allí, imho. 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, existe la forma de aceptar un vector si busca 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);
    }
}

Sin embargo, no es realmente cómodo pasarlo. Tienes que construir tu vector manualmente y luego pasar ... Veo que ya tienes la sensación correcta acerca de las funciones de estilo Vararg. No se deben usar para este tipo de código y solo cuando se conectan con el código C o las funciones de depuración, si es que se usan. 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 sobrecargas hasta un límite superior aproximadamente de esta manera:

#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 consultar la biblioteca de preprocesadores boost aquí .

La próxima versión de C ++ ofrecerá posibilidades mucho mejores. Se pueden usar las listas de inicializadores:

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

En C ++ siguiente también es posible admitir muchos argumentos arbitrarios (de tipo mixto) utilizando plantillas variadas . GCC4.4 tendrá soporte para ellos. GCC 4.3 ya los apoya parcialmente.

Otros consejos

1) Sí, excepto que AddChar es público, no hay razón para que sea un amigo .

2) Esto es discutible. < < se encuentra en la posición de ser el operador cuya sobrecarga para " weird " Las cosas son al menos aceptadas a regañadientes.

3) Nada obvio. Como siempre, perfilar es tu amigo. Es posible que desee considerar pasar los parámetros de cadena a AddChar y operador < < por referencia const ( const std :: string & amp; ) para evitar Copia innecesaria.

  

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

No lo creo. Es confuso como el infierno para alguien que no sabe que has sobrecargado al operador. Simplemente apégate a los nombres de los métodos descriptivos y olvídate de los caracteres adicionales que estás escribiendo, simplemente no vale la pena. Su mantenedor (o usted mismo en 6 meses) se lo agradecerá.

Prefiero no sobrecargarlo de esa manera personalmente porque los vectores normalmente no tienen un operador de turno izquierdo sobrecargado, no es realmente el idioma ;-)

Probablemente devolvería una referencia de AddChar así:

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

para que puedas hacer

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

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

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

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 tampoco lo tiene para empujar elementos, por buenas razones.

Si le preocupa que el código de la persona que llama sea demasiado largo, podría considerar esto en lugar del operador sobrecargado:

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

Esto será posible si tienes AddChar () return * this .

Es gracioso que tengas esta función toString () . En el caso de ese , un operador de < < para enviar a un flujo sería lo estándar a utilizar en su lugar. Por lo tanto, si desea usar operadores, haga que la función toString () sea un operador < < .

El operador no está sobrecargado correctamente aquí. No hay razón para hacer que el operador sea 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 < < for ostream para que el objeto pueda enviarse a cout o ofstreams).

Lo que realmente quieres que sea el operador:

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

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

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

Esto va a hacer que las cosas sean bastante confusas, usaría la misma sintaxis que std :: cin en una variable:

std :: cin > > someint;

" Primero " > > contenedor;

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top