Algum humor e C++ Design Patterns (parte 1)

Uma apresentação e descrição bem humorada a respeito dos "design patterns" clássicos de programação implementados em C++. Visa ajudar os desenvolvedores C++ (e em alguns casos os de C, apenas) a deixar o seu código mais bonito, seguro e elegante. Bem, e as piadas não visam ser de mal gosto - se forem, por favor, me avisem, que eu as retiro!

[ Hits: 17.681 ]

Por: Paulo Silva Filho em 13/09/2010 | Blog: http://psfdeveloper.blogspot.com


Pattern Singleton



Se há alguma coisa que eu realmente odeio, essa coisa é uma variável global. Variáveis globais transformam programas em uma massa enrolada e desconexa como um espaguete sem a necessidade de um único comando goto.

O padrão Singleton nos ajuda a evitar variáveis globais e, ainda, inibe alguns erros de linkagem que variáveis globais podem provocar quando compilamos nossos programas. Entre uma variável global e um Singleton, eu sempre prefiro o Singleton, a não ser que seja impossível usá-lo.

Um singleton é apenas uma classe que pode ter um, e apenas um objeto como instância. Toda vez que tentamos acessar uma instância de um Singleton, acessamos sempre a mesma instância.

Uma implementação bastante interessante de Singleton pode ser vista abaixo:

class Singleton {
public:  
    static  Singleton *access();
    virtual int getValue();
    virtual void setValue(const int &val);
protected:
    Singleton() { this->value = 0; }
    ~Singleton() {}
protected:
    int value;
};

Singleton *Singleton::access() {
    static Singleton *single = NULL;
    if(single == NULL) {
        single = new Singleton();
    }
  
    return single;
}

int Singleton::getValue() {
    return this->value;
}

void Singleton::setValue(const int &val) {
    this->value = val;
}

Muito simples o padrão Singleton, não? E muito, muito útil. Qualquer variável global pode ser colocada dentro da classe Singleton, para evitar dados desorganizados espalhados pelo programa todo. Use o padrão Singleton para trazer forte organização ao seu código, a não ser que você goste de lidar com um monte de variáveis globais, uma vez que elas tornam a programação em um jogo de caça ao tesouro - mas, se você perder esse jogo, estará realmente derrotado (perdendo o seu próprio emprego, por exemplo).

Uma das características mais interessantes do Singleton é que tanto o seu Constructor quanto o seu Destructor são variáveis protegidas, dentro da classe. Elas não podem ser acessada fora da própria classe Singleton. Uma vez que um Singleton é instanciado, não faz sentido fornecer um Destructor para apagá-lo da memória. E o Singleton não causará vazamentos de memória, uma vez que ele não se multiplicará dentro do programa (ele é uma instância única, de toda forma), e Singletons, geralmente, são utilizados durante todo o período de execução do sistema. Eles serão desalocados automaticamente pelo sistema operacional quando o programa terminar.

Singletons, geralmente, são muito úteis em contextos de multi-threading. Nesse caso, as funções de acesso aos dados da classe precisam de algum tipo de sincronização, como pode ser visto no exemplo abaixo:

class SynchroSingleton {
public:  
    static  SynchroSingleton *access();
    virtual int getValue();
    virtual void setValue(const int &val);
protected:
    SynchroSingleton()
        { this->value = 0; }
    ~SynchroSingleton() {}
    void lock();
    void unlock();
protected:
    int value;
    /**
      * Variables needed to lock
      * and unlock functions:
      */
    // ...
};

SynchroSingleton
*SynchroSingleton::access() {
    static SynchroSingleton
             *single = NULL;
    if(single == NULL) {
        single =
             new SynchroSingleton();
    }
  
    return single;
}

int SynchroSingleton::getValue() {
    return this->;value;
}

void
SynchroSingleton::setValue
             (const int &val) {
    this->value = val;
}

void SynchroSingleton::lock() {
    /**
     * Specific synchronization code,
     * using Critical Section
     * objects. This is platform
     * specific and outside the scope
     * of this tutorial. In Linux or
     * BSD you can use POSIX threads.
     *
     */
}

void SynchroSingleton::unlock() {
    /**
     * Specific synchronization code,
     * using Critical Section
     * objects. This is platform
     * specific and outside the scope
     * of this tutorial. In Linux or
     * BSD you can use POSIX threads.
     *
     */
}

No exemplo acima, a atualização da variável value não precisa ser sincronizada porque, na maioria dos sistemas, uma atribuição simples a um inteiro é uma instrução atômica à CPU, mas, como o código acima é apenas um exemplo, ele mostra como usar a sincronização em uma instância de um Singleton. O pattern de Multiple-Read, Single Write, que será apresentando futuramente, também pode ser usado em conjunção com o Singleton.

O código a seguir mostra como chamar uma instância do Singleton:

Singleton.cpp:

#include <iostream>

using namespace std;

class Singleton {
public:  
    static  Singleton *access();
    virtual int getValue();
    virtual void setValue
            (const int &val);
protected:
    Singleton() { this->value = 0; }
    ~Singleton() {}
protected:
    int value;
};

Singleton *Singleton::access() {
    static Singleton *single = NULL;
    if(single == NULL) {
        single = new Singleton();
    }
  
    return single;
}

int Singleton::getValue() {
    return this->value;
}

void
Singleton::setValue(const int &val) {
    this->value = val;
}

int main() {
    cout << "Hello world!" << endl;
    Singleton *s = Singleton::access();
    cout << "Singleton address: "
         << (int) s
         << endl;
  
    cout << "Let's load \"another\" "
         << singleton instance..."
         << endl;
  
    Singleton *another_s =
               Singleton::access();
    cout << "New instance singleton "
         << "address: "
         << (int) another_s
         << endl;
    cout << "Then: what's up, Doc?"
         << endl;
  
    return 0;
}

Então, apenas compile o programa e rode:

g++ Singleton.cpp -o sing
$ ./sing

Hello world!
Singleton address: 1048912
Let's load "another" singleton instance...
New instance singleton address: 1048912
Then: what's up, Doc?

Como você pode ver, acima, ambas as chamadas a Singleton::access() retornam o mesmo endereço de memória, portanto o mesmo ponteiro para o mesmo objeto, uma vez que ele não pode ser destruído fora da própria classe Singleton.

No próximo artigo discutiremos a respeito do pattern Inversion of Control.

Créditos:

[1] Esse artigo foi publicado originalmente no meu blog em: http://psfdeveloper.blogspot.com

Página anterior    

Páginas do artigo
   1. Introdução
   2. Pattern Singleton
Outros artigos deste autor

Algum humor e C++ Design Patterns (parte 2)

Leitura recomendada

Sinais em Linux

Alocação dinâmica de memória em C

Instalando Facebook Folly através do Conan

Mapear objetos em C

Programação de Jogos com SDL

  
Comentários
[1] Comentário enviado por julio_hoffimann em 13/09/2010 - 22:04h

Oi Paulo,

Já vi que vou gostar desta série de artigos avançados em C++, é uma ótima oportunidade de trocar experiências. Programo em C++ há um tempo e concordo com você que quando o assunto é linguagem de programação com potencial complexo, ela está entre as mais votadas.

Muitas vezes me deparei com o termo Singleton nos livros, mas passei desapercebido. Não sabia que se tratava de algo tão interessante, vou pesquisar mais sobre o tema. Seu artigo também corrigiu a idéia que tinha de que uma classe para ser instanciada precisava ter seu construtor público.

Uma pergunta, quando você declarou membros virtuais, teve alguma relação com o Singleton? Não vejo porque declarar métodos de acesso como virtuais nesse exemplo.

Outra pergunta, é boa prática utilizar NULL ao invés de 0 em C++?

Parabéns, aguardo os próximos artigos ansioso.

P.S.: Há um erro de digitação na definição do método getValue() no segundo exemplo, um ";" extra.

[2] Comentário enviado por psfdeveloper em 14/09/2010 - 05:22h

Caro Julio,

muito obrigado pelos elogios.

Quanto ao meu uso das classes virtuais, nesse caso não tem nada a ver com o Singleton. Eu apenas o fiz caso alguém ache válido derivar essa classe. Entretanto, você tem razão, e não acredito que essa implementação de singleton seja derivável, porque a função estática access teria de ser reimplemetada para cada derivação.

O uso de NULL em C++ é quase sempre uma boa prática, principalmente no uso de ponteiros. NULL é apenas uma macro que expande para 0, mas aumenta a legibilidade do código, ainda mais em C++, que, juntamente com Perl, compartilham as sintaxes mais horrorosas entre as linguagens de programação. O NULL também evita certos typecastings. Nunca descobri por que, mas, pela minha experiência, a simples mudança de 0 para null evitou que eu tivesse que fazer casts estranhos. Eu costumo usar o NULL sempre, exceto no caso de funções virtuais puras, que uso o zero, mesmo, da seguinte forma:

class A {
public:
// ... algum código
void funcVirtualPura(int arg1, char arg2) = 0;
protected:
// ... algum código
private:
// ... algum código
};

Não acho que o código acima seja ilegível.

Esse artigo que eu postei é bastante simples, apenas para mostrar o que é um Singleton, não para apresentar uma implementação definitiva. Eu estou fazendo a segunda parte do artigo e organizando a biblioteca como um projeto no Sourceforge para facilitar o acesso ao código fonte. Eu preciso do conceito de singleton para implementar a minha versão de Inversion of Control. Na apresentação de Inversion of Control eu apresento uma implementação definitiva de Singleton.

Se você tiver pressa em ver o que estou preparando, eu posto os meus artigos no meu blog sem ter o preciosismo de escrevê-los por completo. Os artigos dos patterns pode ser visto nesse link: http://psfdeveloper.blogspot.com/search/label/%27SOME%20HUMOUR%20AND%20C%2B%2B%20DESIGN%20PATTERNS%2... . Assim que eu terminar o texto de Inversion of Control inteiro, eu vou trazê-lo para o Viva o Linux, como a segunda parte desse tutorial, mas enquanto ele está sendo feito, só publico os seus pedaços no meu blog mesmo.

Um último ponto. C++ é uma linguagem muito estranha. Você pode colocar constructors e destructors com qualquer nível de acesso (o que é possível em Java, também), e certos operadores podem ser chamados até mesmo a partir de funções virtuais. É possível chamar o operador delete de dentro de um módulo do próprio objeto, com a horrorosa sintaxe "delete this;", o que destrói o objeto e invalida o ponteiro. Como o método pertence à classe e não ao objeto, ele termina normalmente, mas, qualquer referência a this, ou ao ponteiro do objeto em qualquer outra escopo, resulta em um segfault.

E, novamente, obrigado pelos elogios.

Abraços.

[3] Comentário enviado por daniel.franca em 14/09/2010 - 11:11h

Uma correção, C e C++ estão sim entre as linguagens mais utilizadas no mundo.
http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

O mais correto acho que seria dizer que C/C++ é pouco usada em T.I. (que é o forte brasileiro)

[4] Comentário enviado por stremer em 14/09/2010 - 20:48h

cara... fazia tempo que não via um artigo TÃO BOM no VOL!!!

Só discordo de uma coisa... C/C++ não são linguagens estranhas... perl pode até ser... ta certo que não são iguais a Java onde ler um código em java é quase que ler um texto em português, mas acho C/C++ até bastante organizado....
Agora Visual Basic sim... aquilo é linguagem estranha.... e olha que trabalhei muitos anos com ela!!!

[5] Comentário enviado por psfdeveloper em 14/09/2010 - 21:26h

Caro Daniel,

eu não conhecia esse link a respeito das estatísticas de popularidade de linguagens, o que faz com o que eu falei a respeito da popularidade de C e de C++ se torne uma besteira enorme. Mas não errei por mal, apenas apresentei uma opinião como um fato, o que nem sempre fica claro para todo mundo.

Agora, eu estudei bem o link da Tiobe, a respeito da popularidade das linguagens de programação. Ela pesquisa o ranking de procura e acessos a páginas cujos conteúdos sejam a respeito de determinada linguagen. Essa pesquisa é feita em diversos sites de procura e de conteúdo, como o Google, a Wikipédia, o Yahoo! e até mesmo o YouTube. Esse registro de popularidade não indica o uso efetivo de determinada linguagem, apesar de, provavelmente, as duas variáveis terem grande correlação. O Tiobe faz um tipo de medida de popularidade, mas o que eu dizia era a respeito do uso efetivo das linguagens entre os programadores.

Dizer que C/C++ é pouco usada em T.I., para mim, soa meio estranho, porque eu não sei qual é o corte entre T.I. e desenvolvimento de software em geral. Eu sei que desenvolvimento de jogos não é TI, apesar de ser tecnologia e programação, mas desenvolvimento das engines dos bancos de dados é TI ou não? Se a TI for restrita ao nosso antigo Processamento de Dados, então você tem razão, caso contrário, você está errado. Segundo a Wikipedia, nesse link: http://pt.wikipedia.org/wiki/Tecnologia_da_informa%C3%A7%C3%A3o, TI engloba todo o uso de computação para lidar com qualquer tipo de dado. O artigo em inglês tem uma definição mais restrita, associando à TI a necessidade de processos e tomada de decisão. De toda forma, estritamente, as duas definições se equivalem.

Na TI como um todo, a base instalada de C e C++ é enorme, assim como a sua comunidade de programadores e desenvolvedores, que, na minha opinião, é a mais influente do mundo, justamente por conta dessa base instalada. Entretanto, assim como popularidade em pesquisas de internet, base instalada não significa exatamente popularidade. C e C++ são fortíssimos em distribuições de software, como sistemas operacionais, bancos de dados, editores de texto, bibliotecas de desenvolvimento porque elas foram feitas com esse intento. No mundo Apple, o Objective-C rivaliza com o C. Mas, fora disso, o uso de C e C++ é anêmico. Por outro lado, a visibilidade de tudo o que é feito em C/C++ é imenso, pois quase tudo que usamos diretamente é feito nessa linguagem. Outro ponto é que, em quase quatorze anos trabalhando com tecnologia, eu só vejo mais e mais pessoas abandonando C/C++ em favor de outras linguagens como Java, C# ou Python, principalmente para Java. Isso não é ruim para C ou C++, pois agora, essas linguagens são usadas de acordo com o seu fim: desenvolvimento de sistemas e de algoritmos em alta performance.

Do Tiobe, o que mais me assustou é o quão pouco as ferramentas da Microsoft são populares nas máquinas de pesquisa. C# está, apenas, em sexto lugar, pouco acima de Python, apesar de sua base instalada ser enorme (Por conta da própria força da Microsoft). Entretanto eu tenho muito mais contato com pessoas programando em .NET do que em C ou C++. Eu tenho uma opinião a esse respeito: quem ja programou usando ferramentas da Microsoft sabe que em Documentação, eles são impecáveis. Se a Apple é fera em interface com o usuário, a Microsoft é fera em dar suporte ao programador. Se eu tenho tal suporte, por que procurar informação a respeito dessas ferramentas?

E a popularidade de C e de C++ no Tiobe pode ser enviesada: se elas são muito procuradas em máquinas de pesquisa, pode ser por pura necessidade acadêmica (algumas faculdades forçam os alunos a estudar C), ou para retirada de dúvidas dessas linguagens que não possuem uma documentação centralizada. E, principalmente para C++, principalmente, porque são difíceis.

Abraços.


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts