c++ - ¿Qué hace static_assert y para qué lo usarías?
debugging c++11 (7)
En ausencia de conceptos, se puede usar static_assert
para una verificación de tipos de 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");
// ...
}
¿Podría dar un ejemplo donde static_assert(...) ''C++0x''
resolvería el problema en la mano con elegancia?
Estoy familiarizado con la afirmación en tiempo de ejecución assert(...)
. ¿Cuándo debería preferir static_assert(...)
sobre regular assert(...)
?
Además, en boost
hay algo llamado BOOST_STATIC_ASSERT
, ¿es lo mismo que static_assert(...)
?
Esto no responde directamente a la pregunta original, pero hace 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) del Diseño C ++ Moderno de Andrei Alexanderscu implementa esta idea de las 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");
La afirmación estática se usa para hacer afirmaciones en tiempo de compilación. Cuando la aserción estática falla, el programa simplemente no compila. Esto es útil en diferentes situaciones, como, por ejemplo, si implementa alguna funcionalidad por código que depende críticamente del objeto unsigned int
que tiene exactamente 32 bits. Puedes poner una afirmación estática como esta
static_assert(sizeof(unsigned int) * CHAR_BIT == 32);
en tu código En otra plataforma, con un tipo unsigned int
tamaño diferente, la compilación fallará, llamando la atención del desarrollador sobre la parte problemática del código y aconsejándoles que lo vuelvan a implementar o vuelvan a inspeccionar.
Para otro ejemplo, es posible que desee pasar algún valor integral como un puntero void *
a una función (un truco, pero útil a veces) y desea asegurarse de que el valor integral se ajuste al puntero
int i;
static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);
Es posible que desee activo ese tipo de caracteres está firmado
static_assert(CHAR_MIN < 0);
o esa división integral con valores negativos redondea hacia cero
static_assert(-5 / 2 == -2);
Y así.
Las aserciones en tiempo de ejecución en muchos casos se pueden usar en lugar de aserciones estáticas, pero las aserciones en tiempo de ejecución solo funcionan en tiempo de ejecución y solo cuando el control pasa por encima de la aserción. Por esta razón, una afirmación de tiempo de ejecución anómala puede permanecer latente, no detectada durante largos periodos de tiempo.
Por supuesto, la expresión en aserción estática debe ser una constante en tiempo de compilación. No puede ser un valor de tiempo de ejecución. Para los valores de tiempo de ejecución, no tiene otra opción que usar la assert
ordinaria.
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::Version
se declara como una constante estática, en lugar de ser #define
d (como cabría esperar en una biblioteca de C ++).
Contraste con la necesidad de compilar SomeLibrary
y tu código, vincular todo y ejecutar el ejecutable solo para descubrir que SomeLibrary
30 minutos compilando una versión incompatible de SomeLibrary
.
@Arak, en respuesta a tu comentario: sí, puedes tener static_assert
simplemente sentado donde sea, por el aspecto:
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: static assertion failed: "Foo::bar is too small :("
Lo uso para asegurar que mis suposiciones sobre el comportamiento del compilador, los encabezados, libs e incluso mi propio código son correctos. 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 una clase envolviendo fseek()
stdio.h
, tomé algunos atajos con enum Origin
y compruebo que esos atajos se alinean con las constantes definidas por stdio.h
uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);
Debería preferir static_assert
sobre assert
cuando el comportamiento se define en tiempo de compilación, y no en tiempo de ejecución, como en los ejemplos que he dado arriba. Un ejemplo donde este no es el caso incluiría el parámetro y la verificación del código de retorno.
BOOST_STATIC_ASSERT
es una macro pre-C ++ 0x que genera código ilegal si la condición no se cumple. Las intenciones son las mismas, aunque static_assert
está estandarizado y puede proporcionar mejores diagnósticos del compilador.
Un uso de static_assert
podría ser asegurar que una estructura (que sea una interfaz con el mundo exterior, como una red o un archivo) tenga exactamente el tamaño que usted espera. Esto captaría los casos en que alguien agrega o modifica a un miembro de la estructura sin darse cuenta de las consecuencias. El static_assert
lo recogería y alertaría al usuario.
BOOST_STATIC_ASSERT
es un contenedor multiplataforma para la funcionalidad static_assert
.
Actualmente estoy usando static_assert para aplicar "Concepts" 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 causará un error de tiempo de compilación si no se cumple alguna de las condiciones anteriores.