c++ - ejemplos - Cómo deshabilitar los temporales
c++ online (10)
Éste no genera un error de compilación, sino un error de tiempo de ejecución. En lugar de medir un momento equivocado, obtienes una excepción que también puede ser aceptable.
Cualquier constructor que desee proteger necesita un argumento predeterminado sobre qué set(guard)
se llama.
struct Guard {
Guard()
:guardflagp()
{ }
~Guard() {
assert(guardflagp && "Forgot to call guard?");
*guardflagp = 0;
}
void *set(Guard const *&guardflag) {
if(guardflagp) {
*guardflagp = 0;
}
guardflagp = &guardflag;
*guardflagp = this;
}
private:
Guard const **guardflagp;
};
class Foo {
public:
Foo(const char *arg1, Guard &&g = Guard())
:guard()
{ g.set(guard); }
~Foo() {
assert(!guard && "A Foo object cannot be temporary!");
}
private:
mutable Guard const *guard;
};
Las características son:
Foo f() {
// OK (no temporary)
Foo f1("hello");
// may throw (may introduce a temporary on behalf of the compiler)
Foo f2 = "hello";
// may throw (introduces a temporary that may be optimized away
Foo f3 = Foo("hello");
// OK (no temporary)
Foo f4{"hello"};
// OK (no temporary)
Foo f = { "hello" };
// always throws
Foo("hello");
// OK (normal copy)
return f;
// may throw (may introduce a temporary on behalf of the compiler)
return "hello";
// OK (initialized temporary lives longer than its initializers)
return { "hello" };
}
int main() {
// OK (it''s f that created the temporary in its body)
f();
// OK (normal copy)
Foo g1(f());
// OK (normal copy)
Foo g2 = f();
}
El caso de f2
, f3
y el retorno de "hello"
pueden no ser necesarios. Para evitar el lanzamiento, puede permitir que la fuente de una copia sea temporal, restableciendo el guard
para que ahora nos proteja en lugar de la fuente de la copia. Ahora también ves por qué usamos los indicadores anteriores: nos permite ser flexibles.
class Foo {
public:
Foo(const char *arg1, Guard &&g = Guard())
:guard()
{ g.set(guard); }
Foo(Foo &&other)
:guard(other.guard)
{
if(guard) {
guard->set(guard);
}
}
Foo(const Foo& other)
:guard(other.guard)
{
if(guard) {
guard->set(guard);
}
}
~Foo() {
assert(!guard && "A Foo object cannot be temporary!");
}
private:
mutable Guard const *guard;
};
Las características para f2
, f3
y para el return "hello"
ahora son siempre // OK
.
Para una clase Foo, ¿hay alguna manera de rechazar construirlo sin darle un nombre?
Por ejemplo:
Foo("hi");
Y solo lo permite si le da un nombre, como el siguiente?
Foo my_foo("hi");
La vida del primero es solo la declaración, y el segundo es el bloque adjunto. En mi caso de uso, Foo
está midiendo el tiempo entre el constructor y el destructor. Como nunca me refiero a la variable local, a menudo me olvido de ponerla y accidentalmente cambio la vida útil. Me gustaría obtener un error de tiempo de compilación en su lugar.
¿Qué tal un pequeño truco?
class Foo
{
public:
Foo (const char*) {}
};
void Foo (float);
int main ()
{
Foo ("hello"); // error
class Foo a("hi"); // OK
return 1;
}
Dado que el objetivo principal es prevenir errores, considere esto:
struct Foo
{
Foo( const char* ) { /* ... */ }
};
enum { Foo };
int main()
{
struct Foo foo( "hi" ); // OK
struct Foo( "hi" ); // fail
Foo foo( "hi" ); // fail
Foo( "hi" ); // fail
}
De esa forma no puedes olvidar nombrar la variable y no puedes olvidarte de escribir struct
. Verboso, pero seguro.
Declare un constructor paramétrico como explícito y nadie creará un objeto de esa clase involuntariamente.
Por ejemplo
class Foo
{
public:
explicit Foo(const char*);
};
void fun(const Foo&);
solo se puede usar de esta manera
void g() {
Foo a("text");
fun(a);
}
pero nunca de esta manera (a través de un temporal en la pila)
void g() {
fun("text");
}
Ver también: Alexandrescu, Normas de codificación C ++, artículo 40.
Hace unos años, escribí un parche para el compilador GNU C ++ que agrega una nueva opción de advertencia para esa situación. Esto se rastrea en un artículo de Bugzilla .
Desafortunadamente, GCC Bugzilla es un lugar de enterramiento donde las sugerencias de características bien incluidas en los parches van a morir. :)
Esto fue motivado por el deseo de detectar exactamente el tipo de errores que son el tema de esta pregunta en el código que utiliza objetos locales como artilugios para bloquear y desbloquear, medir el tiempo de ejecución, etc.
Haga que el constructor sea privado pero proporcione a la clase un método de creación .
No, me temo que esto no es posible. Pero podrías obtener el mismo efecto creando una macro.
#define FOO(x) Foo _foo(x)
Con esto en su lugar, puede escribir FOO (x) en lugar de Foo my_foo (x).
Otra solución basada en macro:
#define Foo class Foo
La declaración Foo("hi");
se expande a la class Foo("hi");
, que está mal formado; pero Foo a("hi")
expande a la class Foo a("hi")
, que es correcto.
Esto tiene la ventaja de que es compatible tanto en origen como en binario con el código existente (correcto). (Esta afirmación no es del todo correcta; consulte el Comentario de Johannes Schaub y la siguiente discusión a continuación: "¿Cómo puede saber que es compatible con el código existente? Su amigo incluye su encabezado y tiene void f () {int Foo = 0;} que anteriormente compilaba bien y ahora se compila incorrectamente. Además, cada línea que define una función miembro de la clase Foo falla: clase de vacío Foo :: bar () {} " )
Simplemente no tiene un constructor predeterminado, y requiere una referencia a una instancia en cada constructor.
#include <iostream>
using namespace std;
enum SelfRef { selfRef };
struct S
{
S( SelfRef, S const & ) {}
};
int main()
{
S a( selfRef, a );
}
Tal como es, con su implementación, no puede hacer esto, pero puede usar esta regla para su ventaja:
Los objetos temporales no pueden vincularse a referencias no const
Puede mover el código de la clase a una función independiente que toma un parámetro de referencia no constante. Si lo hace, obtendrá un error de compilación si un temporal intenta vincularse a la referencia no constante.
Muestra de código
class Foo
{
public:
Foo(const char* ){}
friend void InitMethod(Foo& obj);
};
void InitMethod(Foo& obj){}
int main()
{
Foo myVar("InitMe");
InitMethod(myVar); //Works
InitMethod("InitMe"); //Does not work
return 0;
}
Salida
prog.cpp: In function ‘int main()’:
prog.cpp:13: error: invalid initialization of non-const reference of type ‘Foo&’ from a temporary of type ‘const char*’
prog.cpp:7: error: in passing argument 1 of ‘void InitMethod(Foo&)’