O C ++ 17 apresenta o [[nodiscard]]
atributo, que permite aos programadores marcar funções de uma maneira que o compilador produza um aviso se o objeto retornado for descartado por um chamador; o mesmo atributo pode ser adicionado a um tipo de classe inteiro.
Eu li sobre a motivação para esse recurso na proposta original e sei que o C ++ 20 adicionará o atributo a funções padrão como std::vector::empty
, cujos nomes não transmitem um significado inequívoco em relação ao valor de retorno.
É um recurso interessante e útil. De fato, quase parece muito útil. Em todos os lugares em que leio [[nodiscard]]
, as pessoas discutem isso como se você apenas o adicionasse a algumas poucas funções ou tipos e esquece o resto. Mas por que um valor não descartável deve ser um caso especial, especialmente ao escrever um novo código? Um valor de retorno descartado não é tipicamente um bug ou pelo menos um desperdício de recursos?
E não é um dos princípios de design do próprio C ++ que o compilador deve capturar o maior número possível de erros?
Se sim, por que não adicionar [[nodiscard]]
seu próprio código não legado a quase todas as não void
funções e quase todos os tipos de classe?
Eu tentei fazer isso no meu próprio código e funciona bem, exceto que é tão detalhado que começa a parecer Java. Parece muito mais natural fazer com que os compiladores avisem sobre valores de retorno descartados por padrão, exceto nos poucos outros casos em que você marca sua intenção [*] .
Como eu vi zero discussões sobre essa possibilidade em propostas padrão, entradas de blog, perguntas sobre estouro de pilha ou em qualquer outro lugar na internet, devo estar perdendo alguma coisa.
Por que essa mecânica não faria sentido no novo código C ++? A verbosidade é a única razão para não usar em [[nodiscard]]
quase todos os lugares?
[*] Em teoria, você pode ter algo como um [[maydiscard]]
atributo, que também pode ser adicionado retroativamente a funções como printf
nas implementações da biblioteca padrão.
operator =
por exemplo. Estd::map::insert
.const
podem inchar uma classe "simples", caso contrário, (ou melhor, "objeto de dados antigo simples") consideravelmente em C ++.std::vector
oustd::unique_ptr
, das quais você só precisa de membros de dados em sua definição de classe. Eu trabalhei com os dois idiomas; Java é uma linguagem aceitável, mas é mais detalhada.Respostas:
No novo código que não precisa ser compatível com os padrões mais antigos, use esse atributo sempre que for sensato. Mas para C ++,
[[nodiscard]]
faz um padrão ruim. Você sugere:De repente, isso faria com que o código correto existente emitisse muitos avisos. Embora tecnicamente essa mudança possa ser considerada compatível com versões anteriores, já que qualquer código existente ainda é compilado com êxito, isso seria uma enorme mudança de semântica na prática.
As decisões de design para uma linguagem existente e madura, com uma base de código muito grande, são necessariamente diferentes das decisões de design para uma linguagem completamente nova. Se esse fosse um novo idioma, o aviso por padrão seria sensato. Por exemplo, a linguagem Nim exige que valores desnecessários sejam descartados explicitamente - isso seria semelhante a agrupar todas as instruções de expressão em C ++ com uma conversão
(void)(...)
.Um
[[nodiscard]]
atributo é mais útil em dois casos:se uma função não tem efeitos além de retornar um determinado resultado, ou seja, é pura. Se o resultado não for utilizado, a chamada certamente será inútil. Por outro lado, descartar o resultado não seria incorreto.
se o valor de retorno deve ser verificado, por exemplo, para uma interface do tipo C que retorna códigos de erro em vez de lançar. Este é o caso de uso principal. Para C ++ idiomático, isso será bastante raro.
Esses dois casos deixam um espaço enorme de funções impuras que retornam um valor, onde esse aviso seria enganoso. Por exemplo:
Considere um tipo de dados da fila com um
.pop()
método que remove um elemento e retorna uma cópia do elemento removido. Esse método geralmente é conveniente. No entanto, existem alguns casos em que apenas queremos remover o elemento, sem obter uma cópia. Isso é perfeitamente legítimo, e um aviso não seria útil. Um design diferente (comostd::vector
) divide essas responsabilidades, mas possui outras vantagens e desvantagens.Observe que, em alguns casos, é necessário fazer uma cópia de qualquer maneira, portanto, graças ao RVO, a devolução da cópia seria gratuita.
Considere interfaces fluentes, nas quais cada operação retorna o objeto para que outras operações possam ser executadas. Em C ++, o exemplo mais comum é o operador de inserção de fluxo
<<
. Seria extremamente complicado adicionar um[[nodiscard]]
atributo a cada<<
sobrecarga.Esses exemplos demonstram que tornar o código C ++ idiomático compilado sem avisos sob a linguagem "C ++ 17 com nodiscard por padrão" seria bastante tedioso.
Observe que seu novo e brilhante código C ++ 17 (onde você pode usar esses atributos) ainda pode ser compilado junto com as bibliotecas que têm como alvo os padrões C ++ mais antigos. Essa compatibilidade com versões anteriores é crucial para o ecossistema C / C ++. Portanto, tornar o nodiscard o padrão resultaria em muitos avisos para casos de uso idiomáticos típicos - avisos que você não pode corrigir sem grandes alterações no código-fonte da biblioteca.
Indiscutivelmente, o problema aqui não é a alteração da semântica, mas que os recursos de cada padrão C ++ se aplicam a um escopo por unidade de compilação e não a um escopo por arquivo. Se / quando algum padrão C ++ futuro se afastar dos arquivos de cabeçalho, essa alteração seria mais realista.
fonte
pop
oroperator<<
, aquelas seriam explicitamente marcadas pelo meu[[maydiscard]]
atributo imaginário , de modo que nenhum aviso seria gerado. Você está dizendo que essas funções geralmente são muito mais comuns do que eu gostaria de acreditar, ou muito mais comuns em geral do que nas minhas próprias bases de código? Seu primeiro parágrafo sobre o código existente é certamente verdadeiro, mas ainda me faz pensar se alguém mais está aplicando todo o seu novo código e[[nodiscard]]
, se não, por que não.returns_an_int() = 0
, então por que deveriareturns_a_str() = ""
funcionar? O C ++ 11 mostrou que essa era realmente uma péssima idéia, porque agora todo esse código estava proibindo movimentos porque os valores eram const. Eu acho que a lição apropriada dessa lição é "não depende de você o que seus chamadores fazem com o resultado". Ignorar o resultado é uma coisa perfeitamente válida que os chamadores podem querer fazer e, a menos que você saiba que está errado (uma barra muito alta), você não deve bloqueá-lo.empty()
também é sensível porque o nome é bastante ambíguo: é um predicadois_empty()
ou é um mutadorclear()
? Você normalmente não espera que esse mutador retorne nada, portanto um aviso sobre um valor de retorno pode alertar o programador sobre um problema. Com um nome mais claro, esse atributo não seria tão necessário.[[pure]]
atributo, e isso deveria implicar[[nodiscard]]
. Dessa forma, fica claro por que esse resultado não deve ser descartado. Mas, além de funções puras, não consigo pensar em outra classe de funções que devam ter esse comportamento. E a maioria das funções não é pura, portanto, não acho que o nodiscard como padrão é a escolha certa.Razões que eu não faria em
[[nodiscard]]
quase todos os lugares:Agora, se você o tornasse padrão, estaria forçando todos os desenvolvedores de bibliotecas a forçar todos os seus usuários a não descartar valores de retorno. Isso seria horrível. Ou você os força a adicionar
[[maydiscard]]
inúmeras funções, o que também é horrível.fonte
Como exemplo: operator << possui um valor de retorno que depende da chamada absolutamente necessária ou absolutamente inútil. (std :: cout << x << y, o primeiro é necessário porque retorna o fluxo, o segundo não serve para nada). Agora compare com printf, onde todos descartam o valor de retorno que existe para verificação de erros, mas o operador << não tem verificação de erros, portanto, não pode ter sido útil em primeiro lugar. Portanto, nos dois casos, o nodiscard seria apenas prejudicial.
fonte
[[nodiscard]]
todos os lugares?".