optional, variant, any
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 89 de 104.
O C++ moderno fornece três tipos de vocabulário nos cabeçalhos <optional>, <variant> e <any> que ajudam você a lidar com valores que podem estar ausentes, conter um de vários tipos ou armazenar qualquer tipo.
std::optional<T> representa um valor que pode ou não existir - perfeito para funções que podem falhar sem lançar uma exceção:
#include <iostream>
#include <optional>
std::optional<int> findIndex(const std::string& str, char c) {
for (size_t i = 0; i < str.size(); ++i) {
if (str[i] == c) return i;
}
return std::nullopt; // No value
}
int main() {
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << "Found at: " << *result << "\n"; // 2
}
}std::variant<Types...> é uma união segura quanto ao tipo (type-safe) que contém exatamente um dos tipos especificados a qualquer momento:
#include <iostream>
#include <variant>
#include <string>
int main() {
std::variant<int, double, std::string> data;
data = 42;
std::cout << std::get<int>(data) << "\n";
data = "hello";
if (std::holds_alternative<std::string>(data)) {
std::cout << std::get<std::string>(data) << "\n";
}
}std::any pode conter um valor de qualquer tipo, determinado em tempo de execução. Use std::any_cast para recuperar o valor:
#include <iostream>
#include <any>
int main() {
std::any value = 10;
std::cout << std::any_cast<int>(value) << "\n";
value = std::string("text");
std::cout << std::any_cast<std::string>(value) << "\n";
}Escolha std::optional para valores anuláveis, std::variant quando você conhece os tipos possíveis em tempo de compilação, e std::any apenas quando você realmente precisar de flexibilidade de tipo em tempo de execução.
Desafio
FácilVamos construir um sistema de configuração que demonstra o poder dos tipos de vocabulário do C++ moderno. Você criará um gerenciador de configurações flexível que lida com valores que podem estar ausentes, podem conter tipos diferentes ou precisam de flexibilidade de tipo em tempo de execução.
Você organizará seu código em três arquivos:
ConfigTypes.h: Defina seus tipos de valores de configuração e funções auxiliares.Crie uma função chamada
parseValueque recebe uma string e tenta analisá-la como um número inteiro. Se a string representar um inteiro válido, retorne o inteiro envolvido emstd::optional. Se a análise falhar (a string contiver caracteres não numéricos), retornestd::nullopt. Para simplificar, considere uma string válida se ela contiver apenas dígitos (e opcionalmente um sinal de menos no início).Também crie um apelido de tipo (type alias) chamado
Settingusandostd::variantque pode conter umint, umdoubleou umastd::string.Por fim, crie uma função chamada
describeSettingque recebe umSettinge retorna uma string descrevendo qual tipo ele contém e seu valor neste formato:- Para int:
Integer: [value] - Para double:
Double: [value] - Para string:
String: [value]
- Para int:
DynamicStore.h: Crie um armazenamento de chave-valor simples usandostd::any.Defina uma classe
DynamicStoreque pode armazenar valores de qualquer tipo. Ela deve ter:- Um método
setque recebe uma chave string e um valorstd::any, armazenando-os internamente (use umstd::map) - Um método
getque recebe uma chave e retorna o valorstd::any(retorne umstd::anyvazio se a chave não existir) - Um método
hasKeyque retorna true se a chave existir
- Um método
main.cpp: Leia três entradas:- Uma string que pode ser um número (para testar
std::optional) - Um indicador de tipo:
int,doubleoustring - Um valor correspondente a esse tipo
Demonstre todos os três tipos de vocabulário:
Primeiro, use sua função
parseValuecom a primeira entrada. Se ela contiver um valor, imprimaParsed: [value]. Se estiver vazia, imprimaParse failed.Segundo, crie uma variante
Settingbaseada no indicador de tipo. Se o tipo forint, armazene o valor como um inteiro. Se fordouble, armazene-o como um double. Se forstring, armazene-o como uma string. Em seguida, chamedescribeSettinge imprima o resultado.Terceiro, crie um
DynamicStore, armazene a string"config_loaded"sob a chave"status", depois recupere-a e imprima:Status: [value]. Usestd::any_cast<std::string>para extrair o valor.- Uma string que pode ser um número (para testar
Por exemplo, com as entradas 42, int e 100:
Parsed: 42
Integer: 100
Status: config_loadedCom as entradas hello, double e 3.14:
Parse failed
Double: 3.14
Status: config_loadedCom as entradas -15, string e username:
Parsed: -15
String: username
Status: config_loadedLembre-se de incluir os cabeçalhos apropriados: <optional>, <variant>, <any>, <string> e <map> onde necessário. Use std::holds_alternative e std::get para trabalhar com sua variante, e has_value() ou conversão booleana direta para verificar seu opcional.
Folha de consulta
O C++ moderno fornece três tipos de vocabulário para lidar com valores flexíveis:
std::optional<T>
Representa um valor que pode ou não existir. Inclua <optional>:
std::optional<int> findIndex(const std::string& str, char c) {
for (size_t i = 0; i < str.size(); ++i) {
if (str[i] == c) return i;
}
return std::nullopt; // Nenhum valor
}
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << *result; // Desreferenciar para obter o valor
}std::variant<Types...>
Uma união segura quanto ao tipo (type-safe union) que contém exatamente um dos tipos especificados. Inclua <variant>:
std::variant<int, double, std::string> data;
data = 42;
std::cout << std::get<int>(data);
data = "hello";
if (std::holds_alternative<std::string>(data)) {
std::cout << std::get<std::string>(data);
}std::any
Pode conter um valor de qualquer tipo, determinado em tempo de execução. Inclua <any>:
std::any value = 10;
std::cout << std::any_cast<int>(value);
value = std::string("text");
std::cout << std::any_cast<std::string>(value);Quando usar: std::optional para valores anuláveis, std::variant quando os tipos possíveis são conhecidos em tempo de compilação, std::any para flexibilidade de tipo em tempo de execução.
Experimente você mesmo
#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"
int main() {
// Ler três entradas
std::string input1; // String que pode ser um número (para testar std::optional)
std::string typeIndicator; // Indicador de tipo: int, double, ou string
std::string value; // Valor correspondente a esse tipo
std::cin >> input1;
std::cin >> typeIndicator;
std::cin >> value;
// TODO: Parte 1 - Testar std::optional com parseValue
// Usar a função parseValue com input1
// Se contiver um valor, imprimir "Parsed: [value]"
// Se estiver vazio, imprimir "Parse failed"
// TODO: Parte 2 - Testar std::variant com Setting
// Criar uma variante Setting baseada em typeIndicator
// Se o tipo for "int", armazenar o valor como inteiro
// Se o tipo for "double", armazenar o valor como double
// Se o tipo for "string", armazenar o valor como string
// Então chamar describeSetting e imprimir o resultado
// TODO: Parte 3 - Testar std::any com DynamicStore
// Criar um DynamicStore
// Armazenar a string "config_loaded" sob a chave "status"
// Recuperá-la e imprimir: "Status: [value]"
// Usar std::any_cast<std::string> para extrair o valor
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico12Recursos do C++ Moderno
Semântica de Movimento e RvaluesPerfect ForwardingExpressões Lambda em Detalhesstd::function & std::bindconstexpr e constevalStructured Bindingsoptional, variant, any