¿Qué hace static_assert y para qué lo usaría?

117

¿Podría dar un ejemplo en el que static_assert(...)('C ++ 11') resolvería el problema con elegancia?

Estoy familiarizado con el tiempo de ejecución assert(...). ¿Cuándo debería preferirlo static_assert(...)al regular assert(...)?

Además, boosthay algo llamado BOOST_STATIC_ASSERT, ¿es lo mismo que static_assert(...)?

AraK
fuente
VEA TAMBIÉN: BOOST_MPL_ASSERT, BOOST_MPL_ASSERT_NOT, BOOST_MPL_ASSERT_MSG, BOOST_MPL_ASSERT_RELATION [ boost.org/doc/libs/1_40_0/libs/mpl/doc/refmanual/asserts.html] para obtener más opciones. _MSG es especialmente bueno una vez que descubres cómo usarlo.
KitsuneYMG

Respuestas:

82

La parte superior de mi cabeza...

#include "SomeLibrary.h"

static_assert(SomeLibrary::Version > 2, 
         "Old versions of SomeLibrary are missing the foo functionality.  Cannot proceed!");

class UsingSomeLibrary {
   // ...
};

Suponiendo que SomeLibrary::Versionse declare como una constante estática, en lugar de ser #defined (como cabría esperar en una biblioteca de C ++).

En contraste con tener que compilar realmente SomeLibrarysu código, vincular todo y ejecutar el ejecutable solo entonces, descubra que pasó 30 minutos compilando una versión incompatible de SomeLibrary.

@Arak, en respuesta a su comentario: sí, puede static_assertsimplemente sentarse donde sea, por lo que parece:

class Foo
{
    public: 
        static const int bar = 3;
};

static_assert(Foo::bar > 4, "Foo::bar is too small :(");

int main()
{ 
    return Foo::bar;
}
$ g ++ --std = c ++ 0x a.cpp
a.cpp: 7: error: la aserción estática falló: "Foo :: bar es demasiado pequeña :("
Mark Rushakoff
fuente
1
Estoy un poco confundido, ¿puede ponerlo static_asserten un contexto de no ejecución? Parece un ejemplo muy bonito :)
AraK
3
Sí, las afirmaciones estáticas, tal como están, generalmente se implementan como la creación de un objeto que solo se define si el predicado es verdadero. Esto solo haría un global.
GManNickG
No estoy seguro de que esto califique como una respuesta a la pregunta original en su totalidad, pero una buena demostración
Matt Joiner
2
Esta respuesta no proporcionó ningún detalle sobre lo que es la diferencia entre la aserción de <cassert> y static_assert
bitek
11
@monocoder: Vea el párrafo que comienza con "Contraste con ...". En resumen: assert comprueba su condición en tiempo de ejecución y static_assert comprueba su condición en la compilación. Entonces, si la condición que está afirmando se conoce en tiempo de compilación, use static_assert. Si no se conocerá la condición hasta que se ejecute el programa, utilice assert.
Mike DeSimone
131

La aserción estática se usa para hacer aserciones en tiempo de compilación. Cuando la aserción estática falla, el programa simplemente no se compila. Esto es útil en diferentes situaciones, como, por ejemplo, si implementa alguna funcionalidad por código que depende críticamente de que el unsigned intobjeto tenga exactamente 32 bits. Puedes poner una aserción estática como esta

static_assert(sizeof(unsigned int) * CHAR_BIT == 32);

en su código. En otra plataforma, con un unsigned inttipo de letra de tamaño diferente, la compilación fallará, lo que llamará la atención del desarrollador sobre la parte problemática del código y le aconsejará que lo vuelva a implementar o lo vuelva a inspeccionar.

Para otro ejemplo, es posible que desee pasar algún valor integral como un void *puntero a una función (un truco, pero útil a veces) y desea asegurarse de que el valor integral quepa en el puntero

int i;

static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);

Es posible que desee que el activo de ese chartipo esté firmado

static_assert(CHAR_MIN < 0);

o que la división integral con valores negativos se redondea hacia cero

static_assert(-5 / 2 == -2);

Y así.

En muchos casos, las aserciones en tiempo de ejecución se pueden usar en lugar de las aserciones estáticas, pero las aserciones en tiempo de ejecución solo funcionan en tiempo de ejecución y solo cuando el control pasa sobre la aserción. Por esta razón, una aserción de tiempo de ejecución fallida puede permanecer inactiva, sin ser detectada durante períodos de tiempo prolongados.

Por supuesto, la expresión en aserción estática tiene que ser una constante en tiempo de compilación. No puede ser un valor en tiempo de ejecución. Para los valores de tiempo de ejecución, no tiene otra opción que utilizar el ordinario assert.

Hormiga
fuente
3
¿No se REQUIERE static_assert para tener un literal de cadena como segundo parámetro?
Trevor Hickey
3
@Trevor Hickey: Sí, lo es. Pero no estaba tratando de referirme static_assertespecíficamente a C ++ 11. Lo static_assertanterior es solo una implementación abstracta de aserción estática. (Yo personalmente uso algo así en el código C). Mi respuesta pretende ser sobre el propósito general de las aserciones estáticas y su diferencia con las aserciones en tiempo de ejecución.
2013
En el primer ejemplo, está asumiendo que no hay bits de relleno en una variable de tipo unsigned int. Esto no está garantizado por el estándar. Una variable de tipo unsigned intpodría ocupar legalmente 32 bits de memoria, dejando 16 de ellos sin usar (y por lo tanto la macro UINT_MAXsería igual a 65535). Entonces, la forma en que describe la primera afirmación estática (" unsigned intobjeto que tiene exactamente 32 bits") es engañosa. Para que coincida con su descripción, esta afirmación debe ser incluido también: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS
@TrevorHickey ya no (C ++ 17)
luizfls
13

Lo uso para asegurarme de que mis suposiciones sobre el comportamiento del compilador, los encabezados, las bibliotecas e incluso mi propio código sean correctas. Por ejemplo, aquí verifico que la estructura se ha empaquetado correctamente al tamaño esperado.

struct LogicalBlockAddress
{
#pragma pack(push, 1)
    Uint32 logicalBlockNumber;
    Uint16 partitionReferenceNumber;
#pragma pack(pop)
};
BOOST_STATIC_ASSERT(sizeof(LogicalBlockAddress) == 6);

En un envoltorio de clase stdio.h's fseek(), he tomado algunos atajos con enum Originy cheque que esos atajos se alinean con las constantes definidas porstdio.h

uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
    BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);

Usted debe preferir static_assertmás assertcuando el comportamiento se define en tiempo de compilación y no en tiempo de ejecución, como los ejemplos que he dado anteriormente. Un ejemplo en el que este no es el caso incluiría la verificación de parámetros y códigos de retorno.

BOOST_STATIC_ASSERTes una macro pre-C ++ 0x que genera código ilegal si no se cumple la condición. Las intenciones son las mismas, aunque static_assertestán estandarizadas y pueden proporcionar mejores diagnósticos del compilador.

Matt Joiner
fuente
9

BOOST_STATIC_ASSERTes un contenedor multiplataforma para la static_assertfuncionalidad.

Actualmente estoy usando static_assert para hacer cumplir "Conceptos" en una clase.

ejemplo:

template <typename T, typename U>
struct Type
{
  BOOST_STATIC_ASSERT(boost::is_base_of<T, Interface>::value);
  BOOST_STATIC_ASSERT(std::numeric_limits<U>::is_integer);
  /* ... more code ... */
};

Esto provocará un error de tiempo de compilación si no se cumple alguna de las condiciones anteriores.

nurettin
fuente
3
Ahora que C ++ 11 está fuera (y ha estado fuera por un tiempo), static_assert debería ser compatible con las versiones más recientes de todos los compiladores principales. Para aquellos de nosotros que no podemos esperar a C ++ 14 (que con suerte contendrá restricciones de plantilla), esta es una aplicación muy útil de static_assert.
Collin
7

Un uso de static_assertpodría ser asegurarse de que una estructura (que es una interfaz con el mundo exterior, como una red o un archivo) tenga exactamente el tamaño esperado. Esto detectaría casos en los que alguien agrega o modifica un miembro de la estructura sin darse cuenta de las consecuencias. El static_assertlo recogería y alertaría al usuario.

Greg Hewgill
fuente
3

En ausencia de conceptos, se puede usar static_assertpara la verificación de tipos en tiempo de compilación simple y legible, por ejemplo, en plantillas:

template <class T>
void MyFunc(T value)
{
static_assert(std::is_base_of<MyBase, T>::value, 
              "T must be derived from MyBase");

// ...
}
vladon
fuente
2

Esto no responde directamente a la pregunta original, pero constituye un estudio interesante sobre cómo hacer cumplir estas comprobaciones de tiempo de compilación antes de C ++ 11.

El Capítulo 2 (Sección 2.1) de Diseño C ++ moderno de Andrei Alexanderscu implementa esta idea de aserciones en tiempo de compilación como esta

template<int> struct CompileTimeError;
template<> struct CompileTimeError<true> {};

#define STATIC_CHECK(expr, msg) \
{ CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; } 

Compare la macro STATIC_CHECK () y static_assert ()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
senderos nocturnos
fuente
-2

Se static_assertpuede usar para prohibir el uso de la deletepalabra clave de esta manera:

#define delete static_assert(0, "The keyword \"delete\" is forbidden.");

Todo desarrollador moderno de C ++ puede querer hacer eso si quiere usar un recolector de basura conservador usando solo clases y estructuras que sobrecargan al operador new para invocar una función que asigna memoria en el montón conservador del recolector de basura conservador que se puede inicializar y crear instancias invocando alguna función que haga esto al principio de la mainfunción.

Por ejemplo, todo desarrollador de C ++ moderno que quiera utilizar el recolector de basura conservador de Boehm-Demers-Weiser, al principio de la mainfunción, escribirá:

GC_init();

Y en todas classy cada una de las structsobrecargas de operator newesta manera:

void* operator new(size_t size)
{
     return GC_malloc(size);
}

Y ahora que operator deleteya no se necesita, porque el recolector de basura conservador de Boehm-Demers-Weiser es responsable de liberar y desasignar cada bloque de memoria cuando ya no se necesita, el desarrollador quiere prohibir la deletepalabra clave.

Una forma es sobrecargar la de delete operatoresta manera:

void operator delete(void* ptr)
{
    assert(0);
}

Pero esto no se recomienda, porque el desarrollador de C ++ moderno sabrá que invocó por error el delete operatortiempo de ejecución, pero es mejor saberlo pronto en tiempo de compilación.

Entonces, en mi opinión, la mejor solución para este escenario es usar static_assertcomo se muestra al comienzo de esta respuesta.

Por supuesto que esto también se puede hacer BOOST_STATIC_ASSERT, pero creo que static_assertes mejor y debería preferirse más siempre.

usuario11962338
fuente