Introdução
float divide(float a, float b)
{
if (b == 0)
{
return ?;
}
return a / b;
}
Aqui está uma função de divisão como exemplo: quando b é 0, é obviamente uma exceção de divisão, mas como retornar esse status ao chamador?
Os métodos comuns incluem o seguinte
- Lança uma exceção. Ao dividir por 0, uma exceção relacionada é lançada. O chamador captura a exceção.
- O retorno de um valor especial (-1, infinito, nullptr) precisa ser acordado com o chamador e a semântica não é muito clara.
- Tipos opcionais.Este é um tipo seguro e mais expressivo.
std::optional
Adicionados no C++ 17, os padrões C++ anteriores podem ser boost::optional
obtidos por meio do . #include <optional>
Você pode usar esse tipo em C++17 .
O que está std::optional
encapsulado ainda é um tipo de valor e pode ser copiado. std::optional
Não há necessidade de solicitar memória no heap.
Quando usar
Normalmente, pode-se usar std::optional
encapsulamento nesses cenários
- Precisa de melhor representação de tipos anuláveis
- Em vez disso, use valores especiais (-1, nullptr, NO_VALUE ou outro)
- Por exemplo, alguns valores podem existir ou não, mas precisamos saber se eles existem. Por exemplo, ao contar idades, pode não haver idade em alguns dados. Nesse caso, podemos usar representação
std::optional<int>
.
- Retorna um resultado de falha de execução
- Por exemplo, quando a divisão por 0 está errada e o valor correto não pode ser obtido.
- Ao carregar recursos preguiçosamente
- Por exemplo, um tipo de recurso não possui um construtor padrão, mas o trabalho de construção é relativamente grande e pode ser definido usando-o
std::optional<Resource>
e carregado quando necessário. - Ao passar parâmetros
- Por exemplo, um tipo de recurso não possui um construtor padrão, mas o trabalho de construção é relativamente grande e pode ser definido usando-o
Exemplo Básico
std::optional<std::string> UI::FindUserNick()
{
if (nick_available)
return {
mStrNickName };
return std::nullopt; // same as return { };
}
// use:
std::optional<std::string> UserNick = UI->FindUserNick();
if (UserNick)
Show(*UserNick);
Criação
// empty:
std::optional<int> oEmpty;
std::optional<float> oFloat = std::nullopt;
// direct:
std::optional<int> oInt(10);
std::optional oIntDeduced(10); // deduction guides
// make_optional
auto oDouble = std::make_optional(3.0);
auto oComplex = make_optional<std::complex<double>>(3.0, 4.0);
// in_place
std::optional<std::complex<double>> o7{
std::in_place, 3.0, 4.0};
// will call vector with direct init of {1, 2, 3}
std::optional<std::vector<int>> oVec(std::in_place, {
1, 2, 3});
// copy/assign:
auto oIntCopy = oInt;
Obter valor
// by operator*
std::optional<int> oint = 10;
std::cout<< "oint " << *opt1 << '\n';
// by value()
std::optional<std::string> ostr("hello");
try
{
std::cout << "ostr " << ostr.value() << '\n';
}
catch (const std::bad_optional_access& e)
{
std::cout << e.what() << "\n";
}
// by value_or()
std::optional<double> odouble; // empty
std::cout<< "odouble " << odouble.value_or(10.0) << '\n';
Uma abordagem segura é determinar se está vazio ao acessar
// compute string function:
std::optional<std::string> maybe_create_hello();
// ...
if (auto ostr = maybe_create_hello(); ostr)
std::cout << "ostr " << *ostr << '\n';
else
std::cout << "ostr is null\n";
Modificar valor
#include <optional>
#include <iostream>
#include <string>
class UserName
{
public:
explicit UserName(const std::string& str) : mName(str)
{
std::cout << "UserName::UserName(\'";
std::cout << mName << "\')\n";
}
~UserName()
{
std::cout << "UserName::~UserName(\'";
std::cout << mName << "\')\n";
}
private:
std::string mName;
};
int main()
{
std::optional<UserName> oEmpty;
// emplace:
oEmpty.emplace("Steve");
// calls ~Steve and creates new Mark:
oEmpty.emplace("Mark");
// reset so it's empty again
oEmpty.reset(); // calls ~Mark
// same as:
//oEmpty = std::nullopt;
// assign a new value:
oEmpty.emplace("Fred");
oEmpty = UserName("Joe");
}