565 votos

Son los días de pasar const std::string & como un parámetro más?

Oí una reciente charla por Herb Sutter, quien sugirió que las razones para aprobar std::vector y std::string por const & son en gran parte desaparecido. Él sugirió que la escritura de una función como la siguiente ahora es preferible:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

Entiendo que el return_val será un r-value en el punto de la función de devoluciones y por lo tanto puede ser devuelto con mover la semántica, que son muy baratos. Sin embargo, inval todavía es mucho más grande que el tamaño de una referencia (que es generalmente implementados como un puntero). Esto es así porque, std::string tiene varios componentes, incluyendo un puntero a la pila y un miembro char[] de cadena corta de optimización. Así que me parece que pasar por referencia es una buena idea.

¿Alguien puede explicar por qué la Hierba podría haber dicho esto?

375voto

Nicol Bolas Puntos 133791

La razón por la Hierba dijo lo que dijo es porque de casos como este.

Digamos que tengo la función A que llama a la función B, que llama a la función C. Y A pasa una cadena a través de B y en C. A no sabe o no le importa C; todos A conoce acerca de es B. Es decir, C es un detalle de implementación de B.

Digamos que Una se define como sigue:

void A()
{
  B("value");
}

Si B y C tienen la cadena const&, entonces se ve algo como esto:

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

Todo muy bien. Sólo está de paso punteros de todo, no copiar, no moverse, todo el mundo feliz. C tarda const& porque no almacenar la cadena. Simplemente utiliza.

Ahora, quiero hacer un simple cambio: C necesita para almacenar la cadena en algún lugar.

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

Hola, constructor de copia y el potencial de asignación de memoria ignorar (SSO). C++11 de mover la semántica se supone que debe hacer lo posible para eliminar el innecesario copia de la construcción, derecho? Y A pasa el temporal; no hay ninguna razón por qué C debería tener que copiar los datos. Sólo debe quedarse con lo que le fue dado.

Excepto que no puede. Debido a que se tarda const&.

Si yo cambio C para tomar su parámetro por valor, que las causas justas B para hacer la copia en el parámetro; de nada me sirve.

Así que si me había pasado str del valor a través de todas las funciones, confiando en std::move reorganización de los datos alrededor, no tendríamos este problema. Si alguien quiere aferrarse a ella, se puede. Si no, bueno.

¿Es más caro? Sí; se mueve en un valor que es más caro que el uso de referencias. Es menos caro que la copia? No para las pequeñas cadenas con SSO. Es que vale la pena hacerlo?

Depende de su caso de uso. ¿Cuánto te odio asignaciones de memoria?

143voto

justin Puntos 72871

Son los días de pasar const std::string & como un parámetro más?

No. Muchas personas toman este consejo (incluyendo a Dave Abrahams'), más allá del ámbito en que se aplica a, y simplificar se aplique a todos los std::string parámetros -- Siempre pasando std::string por el valor no es una "mejor práctica" para cualquier y todos los arbitraria parámetros y aplicaciones debido a las optimizaciones de estas charlas/artículos se centran en aplicar sólo a un conjunto restringido de casos.

Si vas a devolver un valor, mutando el parámetro, o teniendo en cuenta el valor, luego se pasa por valor podría salvar caro copiar y ofrecer sintáctica conveniencia.

Como siempre, paso por referencia constante ahorra mucho la copia cuando usted no necesita una copia.

Ahora, para el ejemplo específico:

Sin embargo inval es todavía bastante más grande que el tamaño de una referencia (que es generalmente implementados como un puntero). Esto es debido a que un std::string tiene varios componentes, incluyendo un puntero a la pila y un miembro char[] para cadena corta de optimización. Así que me parece que pasar por referencia es una buena idea. ¿Alguien puede explicar por qué la Hierba podría haber dicho esto?

Si el tamaño de la pila es una preocupación (y suponiendo que este no es entre líneas/optimizado), return_val + inval > return_val -- IOW, el pico de uso de la pila puede ser reducido por pasar por aquí (nota: simplificación de ABIs). Mientras tanto, el paso por referencia constante puede deshabilitar las optimizaciones. La razón principal aquí no es para evitar el crecimiento de la pila, pero para asegurar la optimización puede realizarse donde sea aplicable.

Los días de paso por referencia constante no son más de ... las reglas más complicadas de lo que una vez fueron. Si el rendimiento es importante, vas a ser prudente considerar cómo usted pasa a estos tipos, partiendo de la información que utiliza en las implementaciones.

59voto

BЈовић Puntos 28674

Esto depende en gran medida de la implementación del compilador.

Sin embargo, también depende de lo que use.

Vamos a considerar siguiente funciones :

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

Estas funciones se llevan a cabo por separado en una unidad de compilación con el fin de evitar inline. Luego :
1. Si pasa un literal de estas dos funciones, no verá mucha diferencia en las interpretaciones. En ambos casos, un objeto string tiene que ser creado
2. Si se pasa a otro std::string objeto, foo2 superan foo1, porque foo1 va a hacer una copia profunda.

En mi PC, usando g++ 4.6.1, me dieron estos resultados :

  • variable por referencia: 1000000000 iteraciones -> tiempo transcurrido: 2.25912 sec
  • variable valor: 1000000000 iteraciones -> tiempo transcurrido: 27.2259 sec
  • literal de referencia: 100000000 iteraciones -> tiempo transcurrido: 9.10319 sec
  • literal de valor: 100000000 iteraciones -> tiempo transcurrido: 8.62659 sec

43voto

bames53 Puntos 38303

A menos que usted realmente necesita una copia todavía es razonable tomar en const &. Por ejemplo:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

Si cambia esto para tomar la cadena de valor, a continuación, usted va a terminar de mover o copiar el parámetro, y no hay necesidad de eso. No sólo es copiar/mover probablemente más caro, pero también introduce un nuevo potencial de la falla; el copiar/mover podría lanzar una excepción (por ejemplo, la asignación durante la copia puede fallar), mientras que tomar una referencia a un valor existente no puede.

Si ¿ necesita una copia, a continuación, pasar y devolver por valor es por lo general (siempre?) la mejor opción. De hecho, yo generalmente no me preocupa en C++03, a menos que las copias extra que realmente causa un problema de rendimiento. Copia elisión parece bastante fiable en los compiladores modernos. Creo que la gente del escepticismo y la insistencia de que usted tiene que comprobar su mesa de soporte para el compilador RVO es en su mayoría obsoletos hoy en día.


En resumen, C++11 en realidad no cambia nada en este sentido, excepto para la gente que no tenía confianza copia elisión.

16voto

Puppy Puntos 90818

std::string no es una VAINA, y su tamaño crudo no es lo más relevante que nunca. Por ejemplo, si usted pasa en una cadena que está por encima de la longitud de SSO y asignados en el montón, yo esperaría que el constructor de copia para copiar el SSO de almacenamiento.

La razón de esto se recomienda porque inval está construido a partir de la argumentación de expresión, y por lo tanto siempre se mueve o copia según corresponda - no hay pérdida de rendimiento, suponiendo que necesita la propiedad de la argumentación. Si no, const referencia podría ser la mejor manera de ir.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X