c++ c++11 c++14 language-lawyer c++17

c++ - Gestionando tipos triviales.



c++11 c++14 (2)

He encontrado que las complejidades de los tipos triviales en C ++ no son triviales para comprender y espero que alguien pueda ilustrarme sobre lo siguiente.

Dado el tipo T , el almacenamiento para T asigna usando ::operator new(std::size_t) o ::operator new[](std::size_t) o std::aligned_storage , y un void * p apunta a una ubicación en ese almacenamiento adecuadamente alineados para T para que pueda construirse en p :

  1. Si std::is_trivially_default_constructible<T>::value mantiene, es el código que invoca un comportamiento indefinido cuando el código omite la inicialización de T en p (es decir, utilizando T * tPtr = new (p) T(); ) antes de acceder de otra manera a *p as T ? ¿Se puede usar T * tPtr = static_cast<T *>(p); En cambio, ¿sin temor a un comportamiento indefinido en este caso?
  2. Si se mantiene el std::is_trivially_destructible<T>::value , ¿omitir la destrucción de T en *p (es decir, llamando a tPtr->~T(); ) causa un comportamiento indefinido?
  3. Para cualquier tipo U para el que se std::is_trivially_assignable<T, U>::value , es std::memcpy(&t, &u, sizeof(U)); equivalente a t = std::forward<U>(u); (para cualquier t de tipo T u de tipo U ) o causará un comportamiento indefinido?

  1. Reinterpretar técnicamente el almacenamiento no es suficiente para introducir un nuevo objeto como. Mire la nota para los estados de constructor por defecto triviales :

Un constructor predeterminado trivial es un constructor que no realiza ninguna acción. Todos los tipos de datos compatibles con el lenguaje C (tipos POD) son trivialmente construibles por defecto. Sin embargo, a diferencia de C, los objetos con constructores por defecto triviales no se pueden crear simplemente reinterpretando el almacenamiento adecuadamente alineado, como la memoria asignada con std :: malloc: placement-new es necesario para introducir formalmente un nuevo objeto y evitar un posible comportamiento indefinido.

Pero la nota dice que es una limitación formal, por lo que probablemente sea segura en muchos casos. Aunque no está garantizado.

  1. No. is_assignable ni siquiera garantiza que la asignación sea legal bajo ciertas condiciones:

Este rasgo no comprueba nada fuera del contexto inmediato de la expresión de la asignación: si el uso de T o U activaría las especializaciones de la plantilla, la generación de funciones de miembro especiales definidas implícitamente, etc., y esas tienen errores, la asignación real puede no compilar incluso si std :: is_assignable :: value compila y evalúa como verdadero.

Lo que describe se parece más a is_trivially_copyable , que dice:

Los objetos de tipos trivialmente copiables son los únicos objetos de C ++ que pueden copiarse con seguridad con std :: memcpy o ser serializados a / desde archivos binarios con std :: ofstream :: write () / std :: ifstream :: read ().

  1. Realmente no lo sé. Me gustaría confiar en los comentarios de KerrekSB.

  1. No, no puedes. No hay ningún objeto de tipo T en ese almacenamiento, y acceder al almacenamiento como si no estuviera definido . Véase también la respuesta de TC here .

    Solo para aclarar la redacción en [basic.life]/1 , que dice que los objetos con inicialización vacía están vivos a partir de la asignación de almacenamiento en adelante: esa redacción obviamente se refiere a la inicialización de un objeto . No hay ningún objeto cuya inicialización esté vacía cuando se asigna almacenamiento sin formato con el operator new o malloc , por lo tanto, no podemos considerarlo "vivo", porque "no" no existe. De hecho, solo se puede acceder a los objetos creados por una definición con inicialización vacía después de que se haya asignado el almacenamiento pero antes de que se produzca la inicialización vacía (es decir, se encuentra su definición).

  2. Omitir llamadas de destructor nunca per se lleva a un comportamiento indefinido. Sin embargo, no tiene sentido intentar ninguna optimización en esta área, por ejemplo, en las plantillas, ya que un destructor trivial simplemente se optimiza.

  3. En este momento, el requisito se puede copiar de forma trivial y los tipos deben coincidir. Sin embargo, esto puede ser demasiado estricto. El N3751 de Dos Reis al menos también propone tipos distintos para trabajar, y me imagino que esta regla se extenderá a la asignación de copias triviales en un tipo en el futuro.

    Sin embargo, lo que has mostrado específicamente no tiene mucho sentido (no solo porque estás pidiendo una asignación a un valor x escalar, que está mal formado), ya que la asignación trivial puede ser válida entre tipos cuya asignación no es realmente " trivial ", es decir, tiene la misma semántica que memcpy . Por ejemplo, is_trivially_assignable<int&, double> no implica que uno pueda ser "asignado" al otro copiando la representación del objeto.