3. Aprimorando a aplicação Alarme com C++ (namespaces)

Nosso programa está estruturado em arquivos de acordo com a nossa separação em componentes. No entanto, mesmo com essa divisão, as funções e variáveis estão todas declaradas em escopo global. Isso implica que não podemos ter duas funções ou variáveis com o mesmo nome e, como possuem acesso global, é passível de confusão entre componentes.

A solução utilizada foi adotar um prefixo diferente para cada componente. Isso deve ser feito de forma manual pelo programador para cada identificador (função ou variável) de cada componente, criando muitas situações de possível erro.

3.1 Namespaces

Em C++, podemos contornar esse problema com o uso de namespaces, delimitando uma região do código que providencia um escopo próprio para os identificadores.

É bastante simples criar um namespace, como podemos ver no trecho definido no escopo global:

namespace first_space {
  int val = 10;
  void foo() {
    printf("Inside first_space\n");
  }
  void bar() {
    printf("val = %d\n", val);
  }
}

namespace second_space {
  void foo() {
    printf("Inside second_space\n");
  }
}

onde foram criados dois namespaces. Note que, como cada um delimita um escopo diferente, é possível declarar duas funções com o mesmo nome (foo), mas com implementações distintas.

Para acessar os identificadores dos namespaces criados, podemos usar o seguinte código:

int main()
{
    first_space::foo();
    second_space::foo();
    first_space::val = 20;
    first_space::bar();

    return 0;
}

cuja saída deve ser

Inside first_space
Inside second_space
val = 20

Se, dentro de um arquivo .cpp, acessamos muitas vezes funções de um mesmo namespace, podemos omitir o nome do namespace usando a sintaxe using namespace. Assim, no nosso exemplo, obteríamos o mesmo resultado escrevendo:

using namespace first_space;

int main()
{
    foo();
    second_space::foo();
    val = 20;
    bar();

    return 0;
}

3.2 Utilizando namespaces no componente Timer

Para nossa aplicação "alarme", como desejamos isolar os identificadores de cada componente, podemos criar namespaces para cada um deles. Dessa maneira, podemos reescrever o arquivo timer.h para:

#ifndef TIMER_H_INCLUDED
#define TIMER_H_INCLUDED

namespace Timer {
   extern void iniciar(int controle);
   extern int timeout();
}

#endif // TIMER_H_INCLUDED

e o arquivo timer.cpp para:

#include <Arduino.h>

#include "definicoes_sistema.h"
#include "timer.h"

#define TEMPO 10

namespace Timer {
   int situacao = false;
   unsigned long horaInicio;
}

void Timer::iniciar(int controle)
{
   situacao = controle;
   if (controle)
   {
      horaInicio = millis();
   }
}

int Timer::timeout()
{
    if (situacao == false)
    {
        return false;
    }
    if(millis() - horaInicio > TEMPO*1000)
    {
        return true;
    }
    return false;
}

Assim removemos a necessidade de usar o prefixo tmr para todas as funções e variáveis. Note que poderíamos declarar as variáveis situacao e horaInicio no header com o modificador extern caso necessitássemos que pudessem ser acessadas em outros arquivos.

Para usar o componente no arquivo principal alarme.ino, temos que modificar o acesso às funções do componente Timer. Assim, a função executarAcao fica:

int executarAcao(int codigoAcao)
{
    int retval;

    retval = NENHUM_EVENTO;
    if (codigoAcao == NENHUMA_ACAO)
        return retval;

    switch(codigoAcao)
    {
    case A01:
        Timer::iniciar(true);
        break;
    case A02:
        sne_bip();
        com_notificar("Alarme em alerta");
        Timer::iniciar(false);
        break;
    case A03:
        com_notificar("Alarme desacionado");
        Timer::iniciar(false);
        break;
    case A04:
        com_notificar("Alarme desacionado");
        break;
    case A05:
        Timer::iniciar(true);
        break;
    case A06:
        sne_acionar(true);
        com_notificar("Invasao");
        Timer::iniciar(false);
        break;
    case A07:
        com_notificar("Alarme desacionado");
        Timer::iniciar(false);
        sne_acionar(false);
        break;
    } // switch

    return retval;
} // executarAcao

Para finalizar o aprimoramento do código, idealmente todos os componentes devem possuir o seu próprio namespace. Isto fica como exercício.

No caso de dúvidas, o código final da aplicação pode ser consultado em

https://gitlab.uspdigital.usp.br/andre.kubagawa/alarme_arduino/-/tree/master/alarme_namespaces

Bibliografia para este capítulo