intermediate_advanced

Leitura de configuração em C++

Uma coisa que é comum no ambiente Java e que eu gosto muito são os arquivos de properties. Não é de hoje que eu os uso para configurar aplicações que eu faço.
Eu tinha uma classe de configuração feita na época do C++98 e que hoje, usando, fiquei com vontade de reescrevê-la para ficar com um aspecto mais atual.

Eu pensei nas seguintes regras:

  1. Eu não preciso manter a mesma assinatura;
  2. Vou tentar fazer da forma mais didática possível;
  3. Vou tentar não repetir código;
  4. Eu quero me divertir fazendo isso;
  5. Vamos falar de Policy-based design.

Antes de começar, vamos falar um pouco sobre o Policy-based design. O que é ?
Este é um paradigma onde, basicamente, nós trocamos todas as implementações concretas por tipos genéricos. Isso provê uma grande flexibilidade às bibliotecas que construímos.

Primeira coisa é definir como seria a estrutura que armazenaria as configurações:

Aqui vemos as nossas primeiras policiesKey, Value e Store. Elas tem valor default, mas poderiam ser quaisquer outras classes.

Temos a nossa estrutura. Vamos agora ler o arquivo de configuração.
A leitura de um arquivo properties é bem simples. Vamos ler linha a linha
e exibir os valores:

Agora já temos as nossas linhas do arquivo sendo mostradas na tela.
Antes de adicionar no config_holder, precisamos fazer um parsing simples para separar o par key=value. Agora começa um pouco de diversão.
Vamos trocar o cout por uma função crack que vai quebrar a linha e gerar um par com chave/valor.

Nós temos aqui um método que vai procurar por um delimitador e retornar os iteradores de início, meio e final, sendo:

  • Início = começo da string (b);
  • Meio = posição do delimitador (pos);
  • Final = final do valor (e).

Ou seja, temos os respectivos intervalos para chave => [b, b+pos) e valor => (b+pos, e). E temos uma função f do tipo AdderFunc que irá receber os iteradores para jogar os valores no mapa.
Repare que não estamos fazendo cópias da string, mas apenas passando os iteradores.

E agora precisamos somente implementar as funções de busca da classe de configuração. Eu pensei nas seguintes funções:

  • get(key) -> retorna valor de key
  • prefix(prefix) -> retorna valores com o prefixo prefix
  • after(prefix) -> retorna o resto das chaves começadas em prefix
  • next_token(prefix, separator) -> retorna o próximo token. Ex: plugin.name

Vamos agora ver a implementação delas.

A função get simplesmente pega o mapa e retorna o valor da chave indicada

Antes, para implementarmos as outras funções, precisamos fazer uma função auxiliar. Esta função tem como objetivo:

  • iterar pelo mapa e comparar se o prefixo é igual ao passado
  • caso seja, invocar um callback passando o par chave/valor como parâmetro (Assim não teremos cópia)

Usando a nossa função auxiliar, a função prefix fica muito mais simples:

As funções after e next_token são pequenas variações da função prefix.
Na primeira, faremos um substring para pegar o resto da chave e na segunda somente o token seguinte.

E vamos mostrar agora como usar a nossa classe de configuração:

Como exemplo de uma mudança de policy, vamos trocar o Value de string para my_value.

Tivemos basicamente que adicionar uma nova classe e seu parser.

Este post foi um pouco mais extenso, onde apenas arranhamos a superfície do Policy-based design. Espero que tenham gostado.

Sobre Policy-based design:
https://en.wikipedia.org/wiki/Policy-based_design

Fontes:
https://github.com/SimplyCpp/examples/blob/master/config.cpp
https://github.com/SimplyCpp/examples/blob/master/config.h
https://github.com/SimplyCpp/examples/blob/master/example.cfg

 

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *