Estou lendo a documentação std::experimental::optional
e tenho uma boa idéia sobre o que faz, mas não entendo quando devo usá-lo ou como devo usá-lo. O site ainda não contém exemplos, o que me dificulta a compreensão do verdadeiro conceito desse objeto. Quando é std::optional
uma boa escolha para usar e como compensa o que não foi encontrado no Padrão anterior (C ++ 11).
c++
boost-optional
c++-tr2
0x499602D2
fonte
fonte
optional
. Imagine que você quer umoptional<int>
ou até mesmo<char>
. Você realmente acha que é necessário que o "Zen" aloque, desreferencie e exclua dinamicamente - algo que de outra forma não exigiria nenhuma alocação e caberia de forma compacta na pilha, e possivelmente até em um registro?std::optional
(embora possa serstd::unique_ptr
). Mais precisamente, a norma exige que T [...] atenda aos requisitos de Destrutível .Respostas:
O exemplo mais simples que consigo pensar:
A mesma coisa pode ser realizada com um argumento de referência (como na assinatura a seguir), mas o uso
std::optional
torna a assinatura e o uso mais agradáveis.Outra maneira de fazer isso é especialmente ruim :
Isso requer alocação dinâmica de memória, preocupação com propriedade etc. - sempre prefira uma das outras duas assinaturas acima.
Outro exemplo:
É extremamente preferível ter algo como a
std::unique_ptr<std::string>
para cada número de telefone!std::optional
fornece a localização dos dados, o que é ótimo para o desempenho.Outro exemplo:
Se a pesquisa não tiver uma determinada chave, podemos simplesmente retornar "sem valor".
Eu posso usá-lo assim:
Outro exemplo:
Isso faz muito mais sentido do que, digamos, ter quatro sobrecargas de função que usam todas as combinações possíveis de
max_count
(ou não) emin_match_score
(ou não)!Ele também elimina o maldito "passe
-1
paramax_count
se você não quiser um limite" ou "passestd::numeric_limits<double>::min()
paramin_match_score
se você não quiser uma pontuação mínima"!Outro exemplo:
Se a string de consulta não estiver inserida
s
, desejo "nãoint
" - não qualquer valor especial que alguém decida usar para esse fim (-1?).Para exemplos adicionais, você pode consultar a
boost::optional
documentação .boost::optional
estd::optional
será basicamente idêntico em termos de comportamento e uso.fonte
std::optional<T>
é apenas umT
e umbool
. As implementações da função membro são extremamente simples. O desempenho não deve ser realmente uma preocupação ao usá-lo - há momentos em que algo é opcional; nesse caso, essa é frequentemente a ferramenta correta para o trabalho.std::optional<T>
é muito mais complexo que isso. Ele usa posicionamentonew
e muito mais outras coisas com alinhamento e tamanho adequados para torná-lo um tipo literal (ou seja, usar comconstexpr
) entre outras coisas. A ingênuaT
e abool
abordagem falhariam rapidamente.union storage_t { unsigned char dummy_; T value_; ... }
Linha 289:struct optional_base { bool init_; storage_t<T> storage_; ... }
Como isso não é "aT
e abool
"? Concordo plenamente que a implementação é muito complicada e não trivial, mas conceitualmente e concretamente o tipo é aT
e abool
. "A ingênuaT
e abool
abordagem falhariam rapidamente". Como você pode fazer essa declaração ao olhar para o código?struct{bool,maybe_t<T>}
o sindicato que está lá para não fazer ostruct{bool,T}
que construiria um T em todos os casos.std::unique_ptr<T>
estd::optional<T>
, em certo sentido, cumprem o papel de "um opcionalT
". Eu descreveria a diferença entre eles como "detalhes de implementação": alocações extras, gerenciamento de memória, localidade dos dados, custo de movimentação, etc. Eu nunca teria,std::unique_ptr<int> try_parse_int(std::string s);
por exemplo, porque causaria uma alocação para todas as chamadas, mesmo que não haja motivo para . Eu nunca teria uma classe com umstd::unique_ptr<double> limit;
- por que fazer uma alocação e perder a localidade dos dados?Um exemplo é citado em Novo artigo adotado: N3672, std :: opcional :
fonte
int
hierarquia de chamada para cima ou para baixo, em vez de passar algum valor "fantasma" "assumido" com o significado de "erro".str2int()
implementar a conversão da maneira que desejar, (B) independentemente do método de obtenção dastring s
, e (C) transmite o significado completo por meiooptional<int>
de uma maneira estúpida de número mágico,bool
/ referência ou alocação dinâmica. Fazendo.Considere quando você está escrevendo uma API e deseja expressar que o valor "sem retorno" não é um erro. Por exemplo, você precisa ler dados de um soquete e, quando um bloco de dados estiver completo, analise-o e devolva-o:
Se os dados anexados concluíram um bloco analisável, você pode processá-lo; caso contrário, continue lendo e anexando dados:
Editar: sobre o restante de suas perguntas:
Quando você calcula um valor e precisa devolvê-lo, torna-se melhor a semântica retornar por valor do que fazer referência a um valor de saída (que pode não ser gerado).
Quando você deseja garantir que o código do cliente tenha que verificar o valor de saída (quem escreve o código do cliente pode não verificar se há erro - se você tentar usar um ponteiro não inicializado, receberá um dump principal; se você tentar usar um inicializado std :: opcional, você recebe uma exceção que pode ser capturada).
Antes do C ++ 11, era necessário usar uma interface diferente para "funções que podem não retornar um valor" - retorne pelo ponteiro e verifique NULL ou aceite um parâmetro de saída e retorne um código de erro / resultado para "não disponível "
Ambos impõem um esforço extra e atenção do implementador do cliente para acertar e ambos são uma fonte de confusão (o primeiro leva o implementador do cliente a pensar em uma operação como uma alocação e exige que o código do cliente implemente a lógica de manipulação de ponteiros e o segundo permite código do cliente para evitar o uso de valores inválidos / não inicializados).
std::optional
cuida bem dos problemas que surgem com as soluções anteriores.fonte
boost::
vez destd::
?boost::optional
porque, após cerca de dois anos de uso, ele é codificado no meu córtex pré-frontal).Costumo usar opcionais para representar dados opcionais extraídos de arquivos de configuração, ou seja, onde esses dados (como com um elemento esperado, ainda que não necessário) em um documento XML) são fornecidos opcionalmente, para que eu possa mostrar de forma explícita e clara se os dados estavam realmente presentes no documento XML. Especialmente quando os dados podem ter um estado "não definido", versus um estado "vazio" e "definido" (lógica difusa). Com um opcional, definir e não definir é desobstruído, também vazio seria desobstruído com o valor de 0 ou nulo.
Isso pode mostrar como o valor de "não definido" não é equivalente a "vazio". Em conceito, um ponteiro para um int (int * p) pode mostrar isso, onde um nulo (p == 0) não está definido, um valor de 0 (* p == 0) está definido e vazio e qualquer outro valor (* p <> 0) está definido como um valor.
Para um exemplo prático, eu tenho uma parte da geometria extraída de um documento XML que tinha um valor chamado sinalizadores de renderização, onde a geometria pode substituir os sinalizadores de renderização (conjunto), desativar os sinalizadores de renderização (definido como 0) ou simplesmente não afetar os sinalizadores de renderização (não definidos), uma opção opcional seria uma maneira clara de representar isso.
Claramente, um ponteiro para um int, neste exemplo, pode atingir o objetivo, ou melhor, um ponteiro de compartilhamento, pois pode oferecer uma implementação mais limpa. Um nulo é sempre um "não definido"? Com um ponteiro, isso não é claro, pois nulo significa literalmente não alocado ou criado, embora possa , mas pode não necessariamente significar "não definido". Vale ressaltar que um ponteiro deve ser liberado e, em boas práticas, definido como 0, no entanto, como um ponteiro compartilhado, um opcional não requer limpeza explícita, portanto, não há uma preocupação de misturar a limpeza com o opcional não foi definido.
Eu acredito que é sobre clareza de código. A clareza reduz o custo de manutenção e desenvolvimento de código. Uma compreensão clara da intenção do código é incrivelmente valiosa.
O uso de um ponteiro para representar isso exigiria sobrecarregar o conceito de ponteiro. Para representar "nulo" como "não definido", normalmente você pode ver um ou mais comentários através do código para explicar essa intenção. Essa não é uma solução ruim em vez de opcional, no entanto, eu sempre opto pela implementação implícita, em vez de comentários explícitos, pois os comentários não são aplicáveis (como na compilação). Exemplos desses itens implícitos para o desenvolvimento (os artigos em desenvolvimento que são fornecidos apenas para reforçar a intenção) incluem os vários lançamentos no estilo C ++, "const" (especialmente em funções-membro) e o tipo "bool", para citar alguns. Pode-se argumentar que você realmente não precisa desses recursos de código, desde que todos obedeçam a intenções ou comentários.
fonte