¿Está `string.assign (string.data (), 5)` bien definido o UB?

11

Un compañero de trabajo quería escribir esto:

std::string_view strip_whitespace(std::string_view sv);

std::string line = "hello  ";
line = strip_whitespace(line);

Dije que regresar string_viewme inquietaba a priori y, además, el alias aquí me parecía UB.

Puedo decir con certeza que line = strip_whitespace(line)en este caso es equivalente a line = std::string_view(line.data(), 5). Creo que llamará string::operator=(const T&) [with T=string_view], que se define como equivalente a line.assign(const T&) [with T=string_view], que se define como equivalente a line.assign(line.data(), 5), que se define para hacer esto:

Preconditions: [s, s + n) is a valid range.
Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).
Returns: *this.

Pero esto no dice qué sucede cuando hay alias.

Ayer hice esta pregunta en el cpplang Slack y obtuve respuestas mixtas. Buscando respuestas súper autorizadas aquí, y / o análisis empírico de implementaciones de vendedores de bibliotecas reales.


Escribí casos de prueba para string::assign, vector::assign, deque::assign, list::assign, y forward_list::assign.

  • Libc ++ hace que todos estos casos de prueba funcionen.
  • Libstdc ++ hace que todos funcionen, excepto que forward_list, por defecto.
  • No sé sobre la biblioteca de MSVC.

La segfault en libstdc ++ me da la esperanza de que esto sea UB; pero también veo que tanto libc ++ como libstdc ++ están haciendo un gran esfuerzo para que esto funcione al menos en los casos comunes.

Quuxplusone
fuente
¿Compiló los casos de prueba con ASan y / o los ejecutó en Valgrind? Eso eliminaría las conjeturas sobre si el código causa violaciones de acceso, aunque aún podría funcionar en la práctica en lugar de por definición.
Konrad Rudolph
1
"Si alguna función miembro u operador de basic_string produce una excepción, esa función u operador no tiene otro efecto en el objeto basic_string". - esto obliga a que ocurra la asignación de almacenamiento antes de que se libere el almacenamiento existente, de modo que se lanza una excepción si la asignación falla, sin alterar *this. Pero no veo nada para evitar que el almacenamiento existente se reutilice, en cuyo caso esto no se especifica, ya que la semántica de copiar el almacenamiento no está especificada.
Sam Varshavchik
2
Para los contenedores de secuencia mencionados, ciertamente es UB, debido a la violación previa de los assignrequisitos en [tab: container.seq.req] .
nogal

Respuestas:

8

Salvo un par de excepciones de las cuales la suya no es una, llamar a una función miembro no constante (es decir assign) en una cadena invalida [...] los punteros a sus elementos. Esto viola la condición previa de assignque [s, s + n)es un rango válido, por lo que este es un comportamiento indefinido.

Tenga en cuenta que string::operator=(string const&)tiene un lenguaje específico para hacer que la autoasignación no sea una operación.

ecatmur
fuente
1
Entonces, ¿cuál es exactamente el punto de invalidación y el punto en el que se requiere que se cumpla la condición previa? La respuesta parece suponer que la condición previa debe cumplirse después de que se haya llamado a la función miembro.
nogal
1
@walnut No soy un abogado de idiomas (ni una persona con un conocimiento de C ++ particularmente extendido), pero cuando invertimos su escenario, podemos hacer una pregunta: ¿podría invalidarse el rango durante la ejecución assign? En caso afirmativo, tendríamos que establecer un punto específico dentro de la implementación de asignar para marcar cuándo puede ocurrir exactamente la invalidación, y creo que eso no es algo que C ++ haría. Aunque podría estar equivocado.
Fureeish
2
@Fureeish Yo tampoco lo sé, pero vea, por ejemplo, el problema 526 de LWG , cerrado como " no es un defecto ", que menciona en su recomendación de cierre que std::vector::insert(iterator pos, const T& value)debe funcionar si valueestá dentro del vector, porque el estándar no especifica que está permitido no funcionar, aunque la referencia pueda invalidar esa referencia.
nogal
1
@walnut " es necesario para el trabajo debido a que la norma no da permiso para que no trabajo. " - el amor es . Entonces ... ¿vale la pena preguntar qué pasa en la práctica ? ¿Se requiere la implementación para hacer una copia del argumento en tal situación? ¿Cómo podría implementarlo de manera realista ...? He escuchado acerca de que los compiladores requieren que lo imposible haga lo imposible. De todos modos, gracias por el comentario!
Fureeish
1
@Fureeish En realidad, mi ejemplo anterior (ahora eliminado) en realidad no estaba probando lo que quería probar. Aquí hay un ejemplo fijo que muestra que tanto libc ++ como libstdc ++ realmente copian antes de pasar a la reasignación según sea necesario.
nogal