4

Si intento crear un sf::SoundBuffer y eliminarlo con su destructor resulta en una excepcion.

Esto llega a ser estresante hasta el punto de que el siguiente codigo este bugeado.

#include <SFML\Audio.hpp>

int main()
{
    sf::SoundBuffer SomeSoundBuffer;

    SomeSoundBuffer.~SoundBuffer();

    return 0;
}

Buscando encontré que tal ves SFML limpie mal la memoria de OpenAL y que al llamar a su destructor, éste no se elimina en el momento, sino que en el próximo Tick.

Incapaz de encontrar información sobre ello lo deje como una posible teoria.

Mi pregunta es simplemente como eliminar el sf::SoundBuffer sin causar un error.

IceBlocker
  • 45
  • 3
  • En C++ no necesitas llamar explícitamente al destructor de ningún objeto (salvo que hayas usado *placement-new*, que no es tu caso). Tu variable `SomeSoundBuffer` llamará automáticamente a su destructor al salir de la función `main`. – PaperBirdMaster May 22 '18 at 06:15

2 Answers2

2

En tu código hay un problema y es que se invoca dos veces el destructor:

  • La primera vez se corresponde con la llamada explícita que has añadido al main
  • La segunda cuando finaliza el programa (la ejecución abandona la instrucción main y ello provoca que se llame al destructor de todos los objetos cuyo ámbito sea la propia función main).

¿Y por qué llamar dos veces al destructor provoca el fallo del programa?

Puede ser algo tan tonto con un borrado doble:

Es bastante probable que la clase sf::SoundBuffer haga uso de memoria dinámica, luego no sería dificil encontrar una implementación similar a la mostrada en el ejemplo siguiente:

class Test
{
  int* ptr;

public:
  Test()
    : ptr{new int(5)}
  { }

  ~Test()
  {
    delete ptr;
  }
};

Aquí tienes una clase de lo más tonta que solo implementa un constructor y un destructor... Y al usarla correctamente no da ningún problema:

int main()
{
  Test t;
}

Sin embargo basta con llamar explícitamente al destructor para que esa clase tan simplona empiece a dar problemas:

int main()
{
  Test t;
  t.~Test();
}

Lo que sucede aquí es que la llamada explícita al destructor libera la memoria asignada a ptr y, posteriormente, se ejecuta un segundo delete sobre el mismo puntero y esto provoca un comportamiento indefinido que, generalmente, se traduce en que el sistema operativo mata tu aplicación para que no corrompa la memoria de otros procesos.

El error se puede subsanar facilmente haciendo que ptr apunte a 0 después del delete:

~Test()
{
  delete ptr;
  ptr = nullptr; /* o, si no compilas con C++11, tambien vale 0 o NULL */
}

Ahora el error ya no se reproducirá porque un delete de un puntero que apunta a 0 no tiene consecuencias... sin embargo siempre será más limpio y sano para tu código evitar hacer cosas raras y dejar al compilador la responsabilidad de ejecutar las funciones especiales (constructores y destructores generalmente). Pero claro, es mucho más sencillo no hacer cosas raras y dejar

eferion
  • 49,291
  • 5
  • 30
  • 72
  • He consultado [el destructor](https://github.com/SFML/SFML/blob/master/src/SFML/Audio/SoundBuffer.cpp#L66) y no hace ningún borrado, pero internamente usa contenedores, que al estar vacíos asumo que tampoco hacen borrados. También he probado el código del autor con objetos con contenedores y en GCC o CLang no ha dado fallo alguno; entonces he recurrido al estándar y ahí estaba: Comportamiento Indefinido. – PaperBirdMaster May 22 '18 at 07:49
  • @Paula_plus_plus fíjate que el constructor llama a `alGenBuffers(1, &m_buffer)` y el destructor a `alDeleteBuffers(1, &m_buffer)`. No puedo probarlo y no localizo las funciones `al*` así que no puedo garantizarlo pero... ¿no habrá un borrado doble de `m_buffer`? – eferion May 22 '18 at 08:03
  • @Paula_plus_plus siempre está lo que has comentado tu de invocar dos veces al destructor, es solo por satisfacer mi curiosidad – eferion May 22 '18 at 08:04
  • Esas llamadas complejas se hacen si `m_buffer` (que es un entero) tiene un valor diferente a `0`, y no lo tiene porque en el constructor por defecto se pone a `0`. – PaperBirdMaster May 22 '18 at 08:07
  • @Paula_plus_plus a eso voy... en el constructor por defecto se llama a `alGenBuffers` pasándole `m_buffer`... pero no localizo la función `alGenBuffers` por via web y no puedo ver qué es lo que hace – eferion May 22 '18 at 08:09
  • Haga lo que haga, no es invocada cuando el destructor se aplica sobre un objeto `SoundBuffer` recién creado. Esa función pertenece a librerías de terceros sobre las que SFML se apoya, [he localizado la definición](https://github.com/SFML/SFML/blob/fae3b65f0567f87fa9925cd42d28df15eb69e79c/extlibs/headers/AL/al.h#L552). – PaperBirdMaster May 22 '18 at 08:11
2

El siguiente código este bugeado

El problema no está en el código si no en tu comprensión del lenguaje C++.


Los objetos de C++ no requieren una llamada explícita a su destructor para ser destruidos1 pues se destruyen automáticamente al finalizar su ciclo de vida.

Así pues llamando al destructor de manera explícita provocas dos destrucciones del objeto creado:

  1. Al llamar al destructor.
  2. Al finalizar su ciclo de vida.

Según el apartado 12.4 del estándar (traducción y resaltado míos) hacer lo que has hecho deriva en comportamiento indefinido:

12.4 Destructores.

  1. Cuando se invoca el destructor de un objeto, el objeto ya no existe; el comportamiento no está definido si el destructor se invoca para un objeto cuyo ciclo de vida ha finalizado [Ejemplo: si el destructor de un objeto automático es explícitamente invocado, y se abandona el bloque de manera que se invoque implícitamente la destrucción del objeto, el comportamiento es indefinidofin del ejemplo ]

El comportamiento indefinido puede hacer que tu código se comporte de manera errática o que aparentemente no suceda nada (lee este hilo para más detalles).

Así pues, el código no tiene ningún bug, se está comportando como se espera. El destructor no debe ser llamado explícitamente porque será llamado implícitamente al finalizar el ciclo de vida de la variable automática, ¿tal vez querías en realidad hacer uso de memoria dinámica?

#include <SFML\Audio.hpp>

int main()
{
    auto SomeSoundBuffer = new sf::SoundBuffer; // <--- crear

    delete SomeSoundBuffer; // <--- destruir explícitamente

    return 0;
}

  1. La llamada explícita a destructor es requerida en muy pocos casos y ninguno de los casos es aplicable al código que has mostrado.
PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82