O que significa “envenenar uma função” em C ++?

96

No final da palestra de Scott Schurr "Apresentando constexpr" na CppCon , ele pergunta "Existe uma maneira de envenenar uma função"? Ele então explica que isso pode ser feito (embora de uma forma não padronizada) por:

  1. Colocando um throwem uma constexprfunção
  2. Declarando um não resolvido extern const char*
  3. Referenciando o não resolvido externnothrow

Sinto que estou um pouco perdido aqui, mas estou curioso:

  • O que significa "envenenar uma função"?
  • Qual é o significado / utilidade da técnica que ele descreve?
sudo make install
fonte
1
Nunca ouvi falar desse termo, esclareça com um exemplo conciso, por favor!
πάντα ῥεῖ
6
@ πάνταῥεῖ, acabei de esclarecer. Este é um termo "amplamente conhecido em pequenos círculos"
SergeyA
4
Ele está falando sobre garantir que todas as chamadas para a constexprfunção sejam avaliadas em tempo de compilação.
TC
@TC Certo - ele mencionou que uma constexprfunção pode ser usada tanto em tempo de compilação quanto em tempo de execução. Então essa é uma forma de forçá-lo para que você não possa usá-lo em tempo de execução? Quando isso é útil?
sudo make install
3
Especialmente em C ++ 11, uma constexprfunção geralmente não é a implementação mais eficiente por causa das restrições, portanto, pode-se não querer que ela seja avaliada em tempo de execução; ou, talvez seja o caso de erro (como em seu exemplo).
TC

Respostas:

106

Em geral, refere-se a tornar uma função inutilizável, por exemplo, se você deseja banir o uso de alocação dinâmica em um programa, você pode "envenenar" a mallocfunção para que ela não possa ser usada.

No vídeo ele está usando de uma forma mais específica, o que fica claro se você ler o slide que é exibido quando ele fala sobre o envenenamento da função, que diz "Uma forma de forçar apenas o tempo de compilação?"

Então, ele está falando sobre "envenenar" a função para torná-la inviável em tempo de execução, portanto, só pode ser chamada em expressões constantes. A técnica é ter um branch na função que nunca é usado quando chamado em um contexto de tempo de compilação, e fazer com que esse branch contenha algo que irá causar um erro.

Uma throwexpressão é permitida em uma função constexpr, desde que nunca seja alcançada durante as invocações em tempo de compilação da função (porque você não pode lançar uma exceção em tempo de compilação, é uma operação inerentemente dinâmica, como alocar memória). Portanto, uma expressão de lançamento que se refere a um símbolo indefinido não será usada durante as invocações em tempo de compilação (porque haveria falha na compilação) e não pode ser usada em tempo de execução, porque o símbolo indefinido causa um erro de vinculador.

Como o símbolo indefinido não é "usado por odr" nas invocações de tempo de compilação da função, na prática o compilador não criará uma referência ao símbolo, portanto, não há problema em ser indefinido.

Isso é útil? Ele está demonstrando como fazer isso, não necessariamente dizendo que é uma boa ideia ou amplamente útil. Se você precisar fazer isso por algum motivo, a técnica dele pode resolver o seu problema. Se você não precisa disso, não precisa se preocupar com isso.

Um motivo pelo qual pode ser útil é quando a versão de tempo de compilação de alguma operação não é tão eficiente quanto poderia ser. Existem restrições sobre os tipos de expressões permitidas em uma função constexpr (especialmente em C ++ 11, algumas restrições foram removidas em C ++ 14). Portanto, você pode ter duas versões de uma função para realizar um cálculo, uma que é ideal, mas usa expressões que não são permitidas em uma função constexpr e uma que é uma função constexpr válida, mas teria um desempenho ruim se chamada em run- Tempo. Você pode envenenar o subótimo para garantir que nunca seja usado para chamadas de tempo de execução, garantindo que a versão mais eficiente (não constexpr) seja usada para chamadas de tempo de execução.

NB O desempenho de uma função constexpr usada em tempo de compilação não é realmente importante, porque ela não tem sobrecarga de tempo de execução de qualquer maneira. Isso pode tornar sua compilação mais lenta, fazendo com que o compilador faça um trabalho extra, mas não terá nenhum custo de desempenho em tempo de execução.

Jonathan Wakely
fonte
1
Eu li o texto do slide, mas não vi a conexão com o termo que ele estava usando. É óbvio agora que você explicou, mas eu não vi na época. Muito obrigado por esta excelente resposta - adoro este site.
sudo make install
@PravasiMeet, faça sua própria pergunta, não desvie dos comentários da pergunta de outra pessoa sobre algo diferente. Uma solução simples seria defini-lo como excluído em todas as unidades de tradução ou substituí-lo por sua própria definição que faz referência a um símbolo indefinido.
Jonathan Wakely
17

'Envenenar' um identificador significa que qualquer referência ao identificador após o 'envenenamento' é um erro rígido do compilador. Esta técnica pode ser usada, por exemplo, para depreciação total (a função ESTÁ obsoleta, nunca use-a!).

Em GCC tradicionalmente houve uma pragma para isso: #pragma GCC poison.

SergeyA
fonte
1
Sim, mas não exatamente no sentido usado naquela palestra.
TC
@TC, ok, provavelmente devo assistir antes de responder :)
SergeyA