2010-07-04 15 views
7

Al examinar las funciones miembro de los contenedores STL, se me ocurrió una idea extraña. ¿Por qué las funciones como std::vector<T>::push_back(T) no tienen un valor de retorno (opcional) (un iterador o incluso una referencia al objeto adjunto)? Sé std::string funciones como insert y erase iteradores de retorno, pero eso es por razones obvias. Creo que a menudo guarda una segunda línea de código que a menudo sigue estas llamadas a funciones.Valores devueltos de la función contenedor STL

Estoy seguro de que los diseñadores de C++ tienen una muy buena razón, por favor me ilumine :)

ACTUALIZACIÓN: Estoy incluyendo un ejemplo de código en el mundo real aquí donde podría reducir la longitud del código:

if(m_token != "{") 
{ 
    m_targets.push_back(unique_ptr<Target>(new Dough(m_token))); 
    return new InnerState(*(m_targets.back()), this); 
} 

podría reducirse a

if(m_token != "{") 
    return new InnerState(*(m_targets.push_back(unique_ptr<Target>(new Dough(m_token)))), this); 

Si asumo std::list::push_back devuelve una referencia al elemento añadido. El código es un poco pesado, pero eso es principalmente (dos conjuntos de paréntesis) debido al constructor de unique_ptr y lo desreferencia. Tal vez para mayor claridad una versión sin ningún tipo de punteros:

if(m_token != "{") 
{ 
    m_targets.push_back(Dough(m_token)); 
    return new InnerState(m_targets.back(), this); 
} 

vs

if(m_token != "{") 
    return new InnerState(m_targets.push_back(Dough(m_token)), this); 

Respuesta

5

No es posible devolver el elemento agregado, o el contenedor en funciones miembro de contenedor una manera segura Los contenedores STL proporcionan principalmente el "strong guarantee". Devolver el elemento manipulado o el contenedor imposibilitaría proporcionar la garantía sólida (solo proporcionaría la "garantía básica"). La razón detrás de esto es que devolver algo podría invocar un constructor de copia, que puede generar una excepción. Pero la función ya salió, por lo que cumplió su tarea principal con éxito, pero aún arrojó una excepción, que es una violación de la garantía fuerte. Quizás pienses: "¡Bueno, entonces regresemos por referencia!", Aunque parezca una buena solución, tampoco es totalmente seguro. Consideramos siguiente ejemplo:

MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass& 

Sin embargo, si el operador de asignación de copia lanza, no sabemos si tuvo éxito o no push_back, violando así indirectamente a la fuerte garantía. Aunque esto no es una violación directa. Por supuesto, usar MyClass& bar = //... en su lugar solucionaría este problema, pero sería bastante inconveniente que un contenedor pudiera entrar en un estado indeterminado, solo porque alguien olvidó un &.

Una muy similar reasoning está detrás del hecho de que std::stack::pop() no devuelve el valor reventado. En cambio, top() devuelve el valor superior de forma segura. después de llamar a la parte superior, incluso cuando se lanza un constructor de copia o un constructor de asignación de copia, aún se sabe que la pila no ha cambiado.

EDIT: yo creo que devuelve un iterador para el elemento recién añadido debe ser perfectamente seguro, si el constructor de copia del tipo de iterador ofrece la garantía de no-tiro (y todos los que conozco hace).

+0

Wow. Gran información, gracias! Todavía me deja preguntándome por qué no devolvieron un iterador por supuesto :). Tal vez porque podría entonces 'MyClass bar = * (myvector.push_back (functionReturningMyClass()));' y probablemente tenga el mismo problema que con devolver una referencia (¿o no?). – rubenvb

+0

Supongo que la razón fueron los problemas de rendimiento, ya que eso requeriría la construcción de un iterador, y la copia de ese iterador, y eso es una sobrecarga, si el valor de retorno no se utiliza. – smerlin

+0

¿No hubiera algún compilador decente que optimizara tal cosa? Probablemente no lo que el comité de estándares estaba mirando :) – rubenvb

-2

No estoy seguro de que tenía una muy buena razón, pero esta función es lento ya basta.

+4

¿Cómo es eso, "lento"? y ¿qué diferencia haría un valor de retorno? –

+0

dado que el valor de retorno se devuelve principalmente en algún registro, existe la posibilidad de que silenciosamente ya devuelva un puntero al elemento insertado. Y no haría mucha diferencia ya que el vector :: back() es constante, por lo que el vector debe guardar un iterador (o un puntero) de todos modos. – flownt

+3

¿Lento comparado con qué? – GManNickG

5

Interesante pregunta. El valor de retorno obvia sería el vector (o lo que sea) que la operación se lleva a cabo en, por lo que podría entonces escribir código como:

if (v.push_back(42).size() > n) { 
    // do something 
} 

Yo personalmente no me gusta este estilo, pero no puedo pensar en una buena razón para no apoyarlo.

+0

Estaba pensando más en las líneas de 'element = v.push_back (i); element.somefunction (someVariable); ', pero el suyo también funcionaría, por supuesto. – rubenvb

0

Creo que tiene que ver con el concepto de un valor de retorno: el valor de retorno no está para su conveniencia sino para un resultado conceptual del 'cálculo' que al parecer conceptualmente push_back no da como resultado.

3

Porque hay .back() que lo devolverá al instante por usted?

Conceptualmente, los diseñadores de C++ no implementarán en una función miembro nada que sea difícil o imposible de implementar en una interfaz pública. Llamar .back() es simple y fácil. Para un iterador, puede hacer (final - 1) o simplemente auto it = end; it--;

El comité de Normas hace posible un nuevo código y simplifica enormemente el código que se usa con mucha frecuencia. Cosas como esta simplemente no están en la lista de cosas que hacer.

+0

Precisión: asegúrese de que haya un elemento en el contenedor antes de llamar a '-' en 'end'. De lo contrario, es un comportamiento indefinido. –

+0

Esa es la misma restricción que tiene '.back()', y una condición posterior de push_back() que tiene éxito (es decir, que no arroja) – MSalters

0

No estoy seguro, pero creo que una de las razones por las que los mutantes std::string miembros devuelven un iterator es por lo que el programador podría obtener un no-const-iterador a la std::string después de una operación mutante sin necesidad de un segundo " fuga".

La interfaz std::basic_string se diseñó para admitir un patrón llamado copy-on-write, que básicamente significa que cualquier operación de mutación no afecta los datos originales, sino una copia.Por ejemplo, si tenía la cadena "abcde" y reemplazó 'a' con 'z' para obtener "zbcde", los datos de la cadena resultante podrían ocupar una ubicación diferente en el montón que los datos de la cadena original.

Si obtiene un no-const iterador de un std::string, a continuación, una aplicación de cadena VACA debe hacer una copia (también llamada "fuga del original"). De lo contrario, el programa puede cambiar los datos subyacentes (y violar la lectura única invariante) with:

char& c0 = *str.begin(); 
c0 = 'z'; 

Pero, después de una operación de mutación de la secuencia, el objeto cadena resultante ya tiene la propiedad única de los datos, por lo que la cadena de la implementación no necesita filtrar sus datos una segunda vez para hacer un iterador no const.

A std::vector es diferente porque no es compatible con la semántica de copiado en escritura.

Nota: Tengo el término pérdida de la implementación libstdC++ de std::basic_string. Además, "filtrar los datos" no significa que la implementación leaks memory.

EDIT: Aquí es la definición de libstdC++ std::basic_string<CharT, Traits, Alloc>::begin() para referencia:

iterator 
begin() 
{ 
    _M_leak(); 
    return iterator(_M_data()); 
} 
2
v.insert(v.end(),x); 

sería equivalente a push_back con la devolución de un iterador. Por qué push_back en sí no devuelve un iterador está más allá de mí.

0

Tal vez porque no fue "necesario"?

erase() y insert() tienen ninguna otra manera que devuelve un iterador para permitir la continuación de un bucle se le llamaba en.

no veo una buena razón para apoyar la misma lógica con push_back().

Pero claro, sería maravilloso hacer más expresiones crípticas. (No veo una mejora en su ejemplo, parece una buena manera de desacelerar a sus compañeros de trabajo cuando lee su código ...)

Cuestiones relacionadas