O que static_assert faz e para que você o usaria?

117

Você poderia dar um exemplo em que static_assert(...)('C ++ 11') resolveria o problema em mãos elegantemente?

Estou familiarizado com o tempo de execução assert(...). Quando devo preferir em static_assert(...)vez de regular assert(...)?

Além disso, boostexiste algo chamado BOOST_STATIC_ASSERT, é o mesmo que static_assert(...)?

AraK
fonte
CONSULTE TAMBÉM: 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 mais opções. _MSG é especialmente bom depois que você descobrir como usá-lo.
KitsuneYMG

Respostas:

82

Em cima da minha cabeça...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

Supondo que SomeLibrary::Versionseja declarado como uma const estática, em vez de ser #defined (como seria de esperar em uma biblioteca C ++).

Contraste com ter que compilar SomeLibrarye seu código, link de tudo, e rodar o executável única , em seguida, para descobrir que você gastou 30 minutos compilar uma versão incompatível SomeLibrary.

@Arak, em resposta ao seu comentário: sim, você pode static_assertsimplesmente ficar sentado em qualquer lugar, pelo 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: erro: falha na afirmação estática: "Foo :: bar é muito pequeno :("
Mark Rushakoff
fonte
1
Estou um pouco confuso, você pode colocar static_assertem um contexto de não execução? Parece um exemplo muito bom :)
AraK
3
Sim, declarações estáticas como estão geralmente são implementadas como a criação de um objeto que só é definido se o predicado for verdadeiro. Isso apenas faria um global.
GManNickG
Não tenho certeza se isso se qualifica como uma resposta à pergunta original na íntegra, mas boa demonstração
Matt Joiner
2
Esta resposta não fornece detalhes sobre qual é a diferença entre assert from <cassert> e static_assert
bitek
11
@monocoder: Veja o parágrafo começando com "Contraste com ...". Resumindo: assert verifica sua condição em tempo de execução, e static_assert verifica sua condição na compilação. Portanto, se a condição que você está afirmando for conhecida em tempo de compilação, use static_assert. Se a condição não for conhecida até que o programa seja executado, use assert.
Mike DeSimone
131

Assert estático é usado para fazer asserções em tempo de compilação. Quando a asserção estática falha, o programa simplesmente não compila. Isso é útil em diferentes situações, como, por exemplo, se você implementar alguma funcionalidade por código que depende criticamente de um unsigned intobjeto ter exatamente 32 bits. Você pode colocar uma declaração estática como esta

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

em seu código. Em outra plataforma, com tipos de tamanhos diferentes, unsigned inta compilação falhará, chamando a atenção do desenvolvedor para a parte problemática do código e aconselhando-os a reimplementar ou inspecionar novamente.

Para outro exemplo, você pode querer passar algum valor integral como um void *ponteiro para uma função (um hack, mas útil às vezes) e você deseja ter certeza de que o valor integral caberá no ponteiro

int i;

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

Você pode querer um ativo que charseja assinado

static_assert(CHAR_MIN < 0);

ou aquela divisão integral com valores negativos arredondados para zero

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

E assim por diante.

Em muitos casos, as asserções de tempo de execução podem ser usadas em vez de asserções estáticas, mas as asserções de tempo de execução só funcionam em tempo de execução e somente quando o controle passa sobre a asserção. Por esse motivo, uma declaração de tempo de execução com falha pode permanecer inativa, sem ser detectada por longos períodos de tempo.

Obviamente, a expressão em asserção estática deve ser uma constante de tempo de compilação. Não pode ser um valor de tempo de execução. Para valores de tempo de execução, você não tem outra escolha a não ser usar o normal assert.

Formiga
fonte
3
Não é static_assert REQUIRED ter uma string literal como um segundo parâmetro?
Trevor Hickey
3
@Trevor Hickey: Sim, é. Mas eu não estava tentando me referir static_assertespecificamente ao C ++ 11. Meu static_assertacima é apenas uma implementação abstrata de asserção estática. (Eu pessoalmente uso algo assim no código C). Minha resposta pretende ser sobre o propósito geral de asserções estáticas e sua diferença com as asserções de tempo de execução.
AnT de
No primeiro exemplo, você está assumindo que não há bits de preenchimento em uma variável do tipo unsigned int. Isso não é garantido pelo padrão. Uma variável do tipo unsigned intpoderia ocupar legalmente 32 bits de memória, deixando 16 deles sem uso (e, portanto, a macro UINT_MAXseria igual a 65535). Portanto, a maneira como você descreve a primeira asserção estática (" unsigned intobjeto com exatamente 32 bits") é enganosa. Para corresponder a sua descrição, esta afirmação deve ser incluída, bem como: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS de
@TrevorHickey não mais (C ++ 17)
luizfls
13

Eu o uso para garantir que minhas suposições sobre o comportamento do compilador, cabeçalhos, libs e até mesmo meu próprio código estejam corretas. Por exemplo, aqui eu verifico se a estrutura foi compactada corretamente no tamanho esperado.

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

Em um embrulho classe stdio.h's fseek(), tenho tido alguns atalhos com enum Origine verificação de que esses atalhos alinhar com as constantes definidas pelostdio.h

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

Você deve preferir static_assertmais assertquando o comportamento é definido em tempo de compilação, e não em tempo de execução, tais como os exemplos que eu dei acima. Um exemplo em que este não seja o caso incluiria a verificação do parâmetro e do código de retorno.

BOOST_STATIC_ASSERTé uma macro pré-C ++ 0x que gera código ilegal se a condição não for satisfeita. As intenções são as mesmas, embora static_assertsejam padronizadas e possam fornecer melhores diagnósticos do compilador.

Matt Joiner
fonte
9

BOOST_STATIC_ASSERT é um wrapper multiplataforma para static_assert funcionalidade.

Atualmente estou usando static_assert para impor "Conceitos" em uma classe.

exemplo:

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 ... */
};

Isso causará um erro de tempo de compilação se qualquer uma das condições acima não for atendida.

nurettin
fonte
3
Agora que o C ++ 11 foi lançado (e já foi lançado há um tempo), static_assert deve ser suportado pelas versões mais recentes de todos os principais compiladores. Para aqueles de nós que não podem esperar pelo C ++ 14 (que esperançosamente conterá restrições de template), este é um aplicativo muito útil de static_assert.
Collin de
7

Um uso de static_assertpode ser para garantir que uma estrutura (que é uma interface com o mundo exterior, como uma rede ou arquivo) tenha exatamente o tamanho que você espera. Isso pegaria casos em que alguém adiciona ou modifica um membro da estrutura sem perceber as consequências. O static_assertpegaria e alertaria o usuário.

Greg Hewgill
fonte
3

Na ausência de conceitos, pode-se usar static_assertpara verificação de tipo de tempo de compilação simples e legível, por exemplo, em modelos:

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

// ...
}
Vladon
fonte
2

Isso não responde diretamente à pergunta original, mas é um estudo interessante sobre como aplicar essas verificações de tempo de compilação antes do C ++ 11.

Capítulo 2 (Seção 2.1) de Modern C ++ Design por Andrei Alexanderscu implementa esta ideia de asserções em tempo de compilação como esta

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

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

Compare a macro STATIC_CHECK () e static_assert ()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
nightlytrails
fonte
-2

O static_assertpode ser usado para proibir o uso da deletepalavra-chave desta forma:

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

Todo desenvolvedor C ++ moderno pode querer fazer isso se quiser usar um coletor de lixo conservador usando apenas classes es e struct s que sobrecarregam o operador new para invocar uma função que aloca memória no heap conservador do coletor de lixo conservador que pode ser inicializado e instanciado invocando alguma função que faça isso no início da mainfunção.

Por exemplo, todo desenvolvedor C ++ moderno que deseja usar o coletor de lixo conservador Boehm-Demers-Weiser mainescreverá no início da função:

GC_init();

E em todo classe structsobrecarregar operator newdesta forma:

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

E agora que o operator deletenão é mais necessário, porque o coletor de lixo conservador Boehm-Demers-Weiser é responsável por liberar e desalocar cada bloco de memória quando não for mais necessário, o desenvolvedor quer proibir odelete palavra chave.

Uma maneira é sobrecarregar delete operatordesta forma:

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

Mas isso não é recomendado, porque o desenvolvedor C ++ moderno saberá que ele invocou por engano o delete operator tempo de execução, mas é melhor saber isso logo no tempo de compilação.

Portanto, a melhor solução para este cenário, na minha opinião, é usar o static_assert conforme mostrado no início desta resposta.

Claro que isso também pode ser feito com BOOST_STATIC_ASSERT, mas acho que static_asserté melhor e deve ser sempre preferido.

user11962338
fonte