2

Estoy intentando detectar, en el destructor, si estoy en una instancia temporal o no.

Lo primero que intenté fue cualificar los destructores:

~S( ) & { ... }
~S( ) && { ... }

Con lo que obtuve un bonito mensaje de error:

error: destructors may not be ref-qualified

A continuación, intenté esto:

#include <iostream>
#include <list>

using namespace std;

struct S {
  S( ) { algo( ); }
  S( const S& ) : S( ) { }
  S( S && ) { algo( ); }

  void realAlgo( bool tmp, bool dest ) const {
    cout << ( tmp ? "Temporal " : "No temporal " );
    cout << ( dest ? "destructor" : "constructor" ) << endl;
  }
  void algo( bool dest = false ) & {
    realAlgo( false, dest );
  }
  void algo( bool dest = false ) && {
    realAlgo( true, dest );
  }

  ~S( ) { algo( true ); }
};

int main( void ) {
  list< S > lista{ S( ), S( S( ) ) };

  cout << "Size: " << lista.size( ) << endl;

  return 0;
}

Para mi sorpresa, la salida obtenida es:

No temporal constructor
No temporal constructor
No temporal constructor
No temporal constructor
No temporal destructor
No temporal destructor
Size: 2
No temporal destructor
No temporal destructor

Es decir, no se llama a la función miembro algo( ) &&.

Tras pensarlo un poco, es un comportamiento lógico, dado que la decisión de llamar a una u otra versión de la función se realiza en tiempo de compilación. Al no poder cualificar los destructores, estos siempre llamaran a la versión & de la función.

Así pues, cambié de táctica. ¿ Porqué no usar un indicador, y establecerlo en el move constructor ?

#include <iostream>
#include <list>

using namespace std;

struct S {
  bool temp;

  S( ) : temp( false ) { cout << "Constructor\n"; }
  S( const S& ) : S( ) { }
  S( S &&o ) : S( ) { o.temp = true; }

  ~S( ) {
    cout << "Destructor de " << ( temp ? "temporal" : "no temporal" ) << endl;
  }
};

int main( void ) {
  list< S > lista{ S( ), S( S( ) ) };

  cout << "Size: " << lista.size( ) << endl;

  return 0;
}

El resultado me volvió a sorprender:

Constructor
Constructor
Constructor
Constructor
Destructor de no temporal
Destructor de no temporal
Size: 2
Destructor de no temporal
Destructor de no temporal

Así pues, la pregunta es:

¿ Como detectar, en el destructor, si la instancia es o no temporal ?

Trauma
  • 25,297
  • 4
  • 37
  • 60

2 Answers2

2

Antes de responder a la pregunta hay que aclarar una serie de conceptos.

Tu programa no va a hacer uso del operador && nunca y la razón es que S(S()) se beneficia de una característica llamada copy elision... es que los compiladores de hoy en día son muy listos los jodidos.

Para forzar una llamada a la sintaxis move tenemos que:

  • Tener un objeto ya creado y hacer una construcción o asignación con std::move
  • Que la construcción o asignación dependa del valor retornado por una función

Algo así:

S func()
{
  S s;
  return s;
}

int main() {
  list< S > lista{ func(),S(std::move(S())) };

  cout << "Size: " << lista.size( ) << endl;

  return 0;
}

Pero claro, ahora el compilador se queja de que no puede aplicar la optimización de copy elision:

prog.cc:30:29: warning: moving a temporary object prevents copy elision [-Wpessimizing-move]
  list< S > lista{ func(),S(std::move(S())) };
                            ^
prog.cc:30:29: note: remove std::move call here
  list< S > lista{ func(),S(std::move(S())) };
                            ^~~~~~~~~~   ~

Podemos observar, sin embargo, que la llamada a func() no da error y sin embargo tampoco produce el resultado esperado... esto es porque aquí sí que está aplicando la ya tan famosa copy elision. Puede ser complicado forzar el uso de la sintaxis move en una única unidad de compilación.

Y bien, dicho esto ya podemos empezar a responder a la pregunta:

¿ Hay algún modo de detectar, dentro del destructor, si estamos sobre una instancia temporal ? Sin usar ninguna variable auxiliar.

La respuesta rápida es NO.

Lo único que sabe el destructor es que debe liberar recursos de una región de memoria dada.


EDITO

respondiendo a la pregunta planteada en la modificación:

¿ Como detectar, en el destructor, si la instancia es o no temporal ?

Tu código no es el más adecuado para probar la sintaxis move. El mecanismo de copy elision es, en este caso, prioritario al ser más óptimo.

Puedes ver tu ejemplo en funcionamiento con un código más sencillo:

#include <iostream>

using namespace std;

struct S {
  bool temp;

  S( ) : temp( false ) { cout << "Constructor\n"; }
  S( const S& ) : S( ) {  }
  S( S &&o ) : S( ) { o.temp = true; }

  ~S( ) {
    cout << "Destructor de " << ( temp ? "temporal" : "no temporal" ) << endl;
  }

    S& operator=(S&& s) { s.temp = true; return *this; }
    S& operator=(S const&) { return *this; }
};

int main( void ) {

  S s;
  s = S();

  return 0;
}
eferion
  • 49,291
  • 5
  • 30
  • 72
1

Sí, es posible poner ese "indicador" en el constructor, lo que sucede es que en el código de tu ejemplo no se está invocando el move compy constructor S(S&&).

Puedes agregar una línea adicional para que se vea:

lista.push_back(S{});