Matrizes Dinâmicas

Agosto 25, 2009

Voltando um pouco as bases vamos analisar neste artigo algumas possibilidades para criação de matrizes dinâmicas usando C++. Tem sido bem comum em fóruns e listas de emails o pessoal dar cabeçada com esse problema, neste artigo vou apresentar as soluções mais conhecidas e as características de cada uma.

Alocando cada Linha

A técnica que parece ser a mais popular é alocar um vetor de ponteiros e depois alocar uma linha da matriz para cada ponteiro deste vetor:


int main(int, char **)
{
    int nlinhas = 5;
    int ncol = 5; 

    int **mat = new int*[nlinhas]; 

    for(int i = 0;i < nlinhas; ++i)
        mat[i] = new int[ncol]; 

    //iniciando ela com zero
    for(int i = 0;i < nlinhas; ++i)
        for(int j = 0;j < ncol; ++j)
            mat[i][j] = 0; 

    //liberar memória
    for(int i = 0;i < nlinhas; ++i)
        delete []mat[i]; 

    delete []mat; 

    return 0;
}

O problema dessa técnica é o grande desperdício de espaço e tempo que ela gera. O desempenho desta é o pior, principalmente pelo fato de serem necessárias varias alocações e alocações de memória em C/C++ são operações que costumam ser bem caras.

Mesmo ignorando o tempo de criação da matriz, o uso dela também vai ser comprometido devido a possibilidade de cada linha dela estar numa região de memória diferente e é bem provável que o cache do processador tenha dificuldades em se manter atualizado por causa disso.

Já o desperdício de espaço ocorre devido a necessidade de se realizar varias alocações e é comum cada alocação precisa alocar um pouco mais de espaço do que o requisitado para a estrutura de controle usada internamente para gerenciar a memória.

A única vantagem em utilizar este método é quando precisamos de uma matriz absurdamente grande e alocamos cada linha apenas quando existe necessidade, mas mesmo nesse caso acredito que existam técnicas melhores para se implementar uma matriz esparsa.

Usando Apenas um Vetor

Uma maneira mais simples de se implementar uma matriz é utilizando apenas um vetor e realizar o acesso dos elementos como se fossem uma matriz:


int main(int, char *argv)

int main(int, char **)
{
    int nlinhas = 5;
    int ncol = 3; 

    //alocando a "matriz"
    int *mat = new int[nlinhas * ncol]; 

    //colocando 5 na linha 3, coluna 2
    mat[3 * ncol + 2] = 5; 

    //liberando a memoria
    delete []mat; 

    return 0;
}

Note como o código nesse caso é muito mais simples que o anterior, o único incomodo deste código é a necessidade de armazenar o tamanho da matriz (o número de linhas para ser mais preciso) e ficar passando ele como parâmetro a todo momento na hora de acessar um elemento, mas não é nada complicado criar uma classe para encapsular isso.

Para acessar um elemento usamos a fórmula: elem[linha * ncol + col], onde linha é o numero da linha que se quer acessar, ncol o número de colunas da matriz e col o número da coluna que se deseja acessar.

Como é feita apenas uma alocação é usado o minimo de memória possível, além de deixar o cache feliz na hora de acessar os dados.

Utilizando std::vector

Por fim, podemos utilizar o std::vector e não precisamos assim nos preocupar mais em gerenciar a memória da matriz e deixar o programa mais alinhado com o RAII:


#include <iostream>
#include <vector>

int main(int, char **)
{
    int ncol = 4;
    int nlinha = 3; 

    std::vector mat(ncol * nlinha);    

    //acessando elemento 2, 1 (linha 2, coluna 1)
    mat[2 * nlinha + 1] = -1; 

    std::cout << mat[2 * nlinha + 0] << std::endl;
    std::cout << mat[2 * nlinha + 1] << std::endl; 

    return 0;
}

Esse código é muito semelhante ao anterior, mas as operações de gerenciamento de memória foram encapsuladas com o uso de um std::vector, utilizando-se uma boa implementação de std::vector não deve existir diferença alguma de performance e uma diferença minima de consumo de memória entre este exemplo e o anterior.

Esta é a maneira mais simples que conheço para lidar com matrizes dinâmicas, mas o ideal mesmo seria construir uma template que encapsule estas operações, mas isto fica para um outro post.


Compilando a Boost no Visual – 2009

Junho 3, 2009

O primeiro post do blog foi exatamente este, e não querendo já parecer sessão da tarde e começar a repetir tudo de novo estou re-fazendo o post.

O post original é bem incompleto em vários aspectos, e ao invés de atualizar ele decidi fazer um completamente novo aproveitando que estou configurando uma maquina de build.

Outra vantagem deste artigo é que indo direto a seção “Configurando o Visual”, as informações ali contidas servem para a maioria das bibliotecas de C/C++.

Baixando a Boost

Esta é a etapa mais simples de todas, basta acessar o site oficial, e clicar no link “Download” do lado direito da tela. A versão 1.39.0 (a ultima lançada até o dia que esse artigo foi publicado) pode ser baixada clicando aqui.

No meu caso utilizo a versão zip do pacote, mas todos os outros pacotes acredito que possuam o mesmo conteúdo.

Após o download extraia o conteúdo dos arquivos para o diretório de bibliotecas que você costuma utilizar, no meu caso c:\develop\libs.

Depois de extrair os arquivos é necessário baixar o bjam, que é a ferramenta de compilação da Boost, a ultima versão pode ser encontrada clicando aqui.

Após concluído o download do bjam extraia o arquivo exe para o mesmo diretório em que a Boost foi colocada  (no meu caso C:\develop\libs\boost_1_39_0).

Compilando

Agora com as ferramentas prontas é necessário rodar o bjam, para tal acesse o console do Visual Studio (não o console padrão do Windows) clicando em “Iniciar” –> Todos os programas –> Microsoft Visual Studio –> Visual Studio Tools –> Visual Studio 2008 Command Prompt.

Com o console aberto abra o diretório da Boost (no meu caso digito: cd \develop\libs\boost_1_39_0). Dentro do diretório da boost digite “bjam” e ENTER.

Agora como já diz o programa, paciência…

Se tudo correu bem, muitos minutos depois a compilação deve estar completa, as vezes aparecem alguns warnings, que eu simplesmente ignoro.

Configurando o Visual

O jeito mais pratico de configurar o visual na minha opinião é criando uma variável de ambiente que contenha o diretório da Boost, fica mais pratico na hora de atualizar as versões (se não quiser criar a variável, basta ir para o próximo paragrafo), então primeiramente clique em “Iniciar” –> Botão direito em “Meu Computador” –> Propriedades.  Na janela que se abrir, clique em “Avançado”, depois no botão “Variáveis de Ambiente”. Na caixa “Variáveis do Sistema”, clique em “Novo”. Agora entre com o nome (ex: BOOST_HOME), e o valor, que é o diretório onde a boost foi instalada (c:\develop\libs\boost_1_39_0 no meu caso), agora é ir clicando em Ok até fechar tudo.

O próximo passo é abrir o visual (se o seu visual já estava aberto e você criou a variável, feche ele e abra novamente). Dentro do visual acesse “Tools” –> “Options”, expanda a linha “Projects and Solutions” –> “VC++ Directories”.

Com a janela de configuração de diretórios podemos configurar os diretórios utilizados pelo visual durante builds, o primeiro a ser configurado é o de include (a ordem não importa aqui), então do lado direito, na caixa de seleção “Show directories for”, selecione “Include Files”. Basta clicar na primeira linha vazia após o ultimo item e entrar com o diretório, que no meu caso foi: $(BOOST_HOME). Caso eu não tivesse criado a variável, o valor teria sido: c:\develop\libs\boost_1_39_0.

Configurando diretórios de include

Por fim é necessário configurar o diretório de bibliotecas selecionando “Libraries Files” na caixa “Show Directories for” e entrando com o diretório das libs, que no meu caso foi: $(BOOST_HOME)\stage\lib, se não estiver usando a variável, basta então: c:\develop\libs\boost_1_39_0\stage\lib, note que o diretório varia de acordo com o diretório que foi usado na instalação, mas o principal é o sub-diretório “stage\lib”.

visual_boost_02

Tendo inserido os diretórios, clique em Ok para salvar.

Testando a Instalação

Para testar a instalação crie um novo projeto no Visual (se não sabe como fazer, clique aqui) e use um código exemplo da Boost, eu utilizei o código abaixo:


#include<boost/filesystem/operations.hpp>
#include<iostream>

namespace bf = boost::filesystem;
int main(int, char **)
{
    bf::path p("first.cpp");
    if(bf::exists(p))
        std::cout<<p.leaf()<<"exists.\n";
    else
        std::cout<<p.leaf()<< "does not exist.\n";

    return 0;
}

O código compilou sem problemas aqui e executou perfeitamente, sendo assim, instalação concluída.

Note que a Boost automaticamente já linka o código com as bibliotecas necessárias, com algumas bibliotecas é necessário configurar o arquivo lib a ser usado, mas isso fica para outro post.


Como Utilizar o Visual Studio C++ – Parte 3

Abril 22, 2009

No artigo anterior vimos de maneira mais detalhada como realizar um build no Visual e diversas configurações, agora vamos expandir um pouco mais nosso ambiente instalando a Windows SDK, que consiste num kit de desenvolvimento para aplicações Windows.

Pode ser que seu projeto não precise utilizar a API do Windows diretamente, mas outras libs podem precisar (Boost por exemplo).

Baixando a SDK

Para baixar a SDK basta acessar o link de download: Windows SDK for Windows Server 2008 and .NET Framework 3.5, para baixar uma imagem de DVD ISO com toda a SDK, use esse link.

No meu caso optei por instalar a versão web, pois como não utilizo todos os componentes não preciso fazer todo o download.

Após o donwload do programa setup, basta rodar ele. Surge então a primeira tela do setup, clicando em next ele pergunta sobre os diretórios de destino (no meu caso, uso o valor padrão).

Em seguida o instalador pergunta sobre os componentes a instalar, no meu caso dispenso qualquer coisa relacionada a desenvolvimento 64 bits (minhas maquinas são todas 32 bits), dispenso também os samples (exemplos) e documentação (que pode ser vista online), minha configuração ficou:

visual_p3_setup_sdk_02

As seleções aqui são um tanto pessoais e dependem do que você pretende fazer, mas o principal a instalar são os componentes do Windows Headers and Libraries, incluindo no minimo os “Header Files” e alguma das “Libraries”. Lembre-se que se for incluído apenas as bibliotecas para CPU 64 bits, será possível apenas compilar código para essa arquitetura.

Clicando em “next”, o instalador vai iniciar o download e a instalação logo após a conclusão deste.

A instalação é bem básica, e apos a conclusão basta clicar em “Finish”.

Testando a Instalação

Se tudo correu bem, agora basta pegar o programa “Hello World” do primeiro artigo da série e modifica-lo:

#include <windows.h>

int main(int argc, char **argv)
{
    MessageBox(NULL, L"Ola Windows!", L"Teste", MB_OK);
    return 0;
}

Agora é compilar e executar o programa, durante a execução deve ser surgir uma dialogo com a mensagem definida acima.

No próximo post vou aproveitar e atualizar o artigo de compilação da Boost, que esta incompleto e desatualizado.


Como utilizar o Visual C++ – Parte 1

Março 6, 2009

O Visual Studio é um pacote de programas da Microsoft para desenvolvimento de software, suportando diversas linguagens como C#, C++, C, Java, Visual Basic, etc. Nesta série de artigos vou focar apenas no Visual C++ Express 2008, mas a maioria da dicas / comandos devem funcionar em outras versões do Visual C++.

As versões express do Visual Studio são versões grátis que a microsoft disponibiliza e obviamente elas não possuem todas as funcionalidades das versões pagas, mas na minha opinião o Visual C++ Express é a melhor ferramenta “grátis” para se trabalhar com C++ no Windows.

Instalando

O primeiro passo é instalar a ferramenta, e o jeito mais simples é indo ao site oficial: Visual C++ Express. Logo ali no lado direito tem um botão “Download Now”, escolha a linguagem (Inglês no meu caso) e clique em Download.

Após concluído o download, execute o programa para inicializar a instalação que a principio é como qualquer outro programa, aceitar licença, instalar algum atalho extra, etc.

A versão atual do instalador pergunta se você quer instalar o Microsoft Silverlight Runtime e o Microsoft SQL Server, nenhum dos dois é necessário, a menos que você queira usar o SQL Server como banco de dados para suas aplicações ou utilizar o Silverlight

Opcionais do Visual C++ Express

Após escolher os adicionais, vem a escolha do diretório de instalação, de novo, é a gosto do fregues. Clicando em “Next” é iniciado o download. Existe no site da Microsoft imagens de CD para quem quiser instalar em um computador que não tenha acesso a rede.

Rodando

Após concluída a instalação, o instalador cria um atalho no menu iniciar, basta então acessar ele e clicar no “Microsoft Visual C++ Express Edition 2008″. O Visual vai carregar e você deve ver uma tela parecida com a abaixo:

Tela inicial do Visual

A versão express costuma solicitar registro, basta seguir o link do dialogo que surgir, entrar com os dados e depois inserir a chave de registro no visual.

Criando a primeira Solução (projeto)

O Visual Studio gerencia o software criado através de soluções, cada solução possui um ou mais projetos, que formam o software ou o conjunto de software sendo criado.

Para criarmos nosso “Hello Visual”, clique em “File” -> “New” -> “Project”. Surge então um dialogo parecido com o abaixo:

Criando novo projeto

Clique em Win32, depois selecione “Win32 Console Application”. Entre com o nome do projeto, diretório onde ele vai ser criado, e nome da solução (que é opcional), em seguida clique em “Ok”.

Configurando o novo projeto

Surge então a primeira tela do Application Wizard, clique em “Next”, na segunda tela selecione “Console Application” e marque a caixa “Empty Project “. Dessa forma é criada uma aplicação vazia, e sem o código de “Hello World” do visual. Depois de criado, o projeto pode ser modificado para aplicação com janela, dll ou biblioteca.

Selecionando o tipo de projeto a ser criado

Agora clique em “Finish” e o projeto vai ser criado.

Após criada a aplicação, deve surgir então o “Solution Explorer”, que é uma janela (que costuma ficar do lado esquerdo da tela) com a visão de todos os projetos e arquivos da sua solução.

Visão do Solution Explorer

No nosso caso ela vai estar vazia, agora criaremos o primeiro arquivo de código: clique com o botão direito do mouse no nome do projeto (meuPrimeiroProjeto, no exemplo), selecione “Add” -> “New Item”. Na janela que aparecer, selecione “C++ File (.cpp)”, entre com o nome do arquivo e clique em “Add”.

Criando novo arquivo para o projeto

Vai surgir então um lindo arquivo em branco, repare no “Solution Explorer” que o arquivo foi adicionado ao seu projeto, agora basta entrar com o código do “Hello World”:

#include <stdio.h>
int main(int argc, char **argv)
{
    printf("Hello Visual");
    return 0;
}

Após entrar com o código, clique na opção “Build” (menu principal) e selecione “Build”, ou então pressione F7. Na parte de baixo da tela vai surgir a tela de output, que mostra o que o compilador esta fazendo, e no final do processo ela indica se houve algum erro ou não. Se aconteceu algum erro, dando um clique duplo sobre a mensagem de erro foca o mesmo na tela.

Programa de teste após compilação

Rodando o “Hello World”

Agora que o projeto já foi compilado, basta executar ele. No menu principal, selecione “Debug” -> “Start Without Debugging”, ou pressione “CTRL + F5″. Pronto, vai surgir uma janela de console com a saída do seu programa.

No proximo post, vamos aprender um pouco mais sobre o build do visual.


Strings e Números

Janeiro 23, 2009

No ultimo post mostrei as operações básicas com strings como contar caracteres e comparações, agora vamos nos aprofundar um pouco mais.

Primeiramente, como funciona um char? O char em C é um int pequeno (geralmente 8 bits) sinalizado (o char pode ser sinalizado ou não, dependendo da plataforma), a questão é que o char armazena números, e os números representam caracteres. Mas qual número é qual? O Visual (e acho que quase todos compiladores) usam como base a tabela ASCII que define o valor numérico de cada caracter.

Com base nisso, podemos então escrever:


#include <stdio.h>

int main(int, char **)
{
    char ola[4];

    ola[0] = 111;
    ola[1] = 108;
    ola[2] = 97;
    ola[3] = 0;

    printf(ola);

    return 0;
}

No código acima inicializamos a string “ola” usando os valores numéricos dela, mas qual a vantagem disso? Nenhuma, na verdade apenas facilitamos a vida do compilador, mas o que pode ser feito de útil com esse conhecimento?

A primeira coisa que pode ser feita é traduzir o outdoor da EA:

ea_canada_tbwa_vancouver_01

 

Agora vamos supor que seja necessário criar um programa para verificar se uma string possui apenas caracteres de A a Z (caixa alta):

#include <iostream>

bool ehAZ(const char *str)
{
    for(int i = 0;str[i]; ++i)
    {
        if((str[i] < 'A') || (str[i] > 'Z'))
            return false;
    }

    return true;
}

int main(int argc, char **argv)
{
    using namespace std;

    cout << ehAZ("ABC") << endl;
    cout << ehAZ("ABaC") << endl;
    cout << ehAZ("!@#$%") << endl;

    return 0;
}

Note que na tabela ASCII letras e números estão em sequência, por isso não precisamos nos preocupar em testar todas as letras, podemos testar apenas sequências.

Convertendo para Letras Maiúsculas

Agora vamos ver como fica uma rotina para mudar todas as letras de uma string para maiúsculas:

#include <iostream>

char *toUp(char *str)
{
    for(int i = 0;str[i]; ++i)
    {
        if((str[i] >= 'a') && (str[i] <= 'z'))
            str[i] = 'A' + (str[i] - 'a');
    }

    return str;    
}

int main(int argc, char **argv)
{
    char str[] = "abCd!@#$456z";

    using namespace std;

    cout << toUp(str) << endl;

    return 0;
}

A função toUp é bem simples, ela percorre a string procurando caracteres que estejam no intervalo de ‘a’ a ‘z’, e quando encontra converte o caractere para sua versão maiúscula. A formula de conversão  primeiramente calcula qual o índice da letra sendo convertida (que seria o número da letra no alfabeto, mas começando de zero) e depois soma esse índice ao índice inicial das letras maiúsculas.

Vamos pegar como exemplo a conversão da letra ‘c’, primeiramente é feito str[i] – ‘a’, o que resulta em ‘c’ – ‘a’, chegando em 99 – 97, resultando 2. Na sequência é feito ‘A’ + 2, que significa: 65 + 2 = 67. Consultando a tabela temos: 67 = C.

Apenas para lembrar, já existe uma função na biblioteca padrão para conversão de letras minusculas para maiúsculas chamada toupper, que é declarada em ctype.h. Neste header também existem outras funções do mesmo genero, como: tolower, islower, isupper, etc.

Convertendo para Números

Por fim, vamos ver como converter uma string que contêm um número inteiro para uma variável to tipo int:

#include <iostream>

int myAtoi(const char *str)
{
    int len = (int) (strlen(str)-1);
    int num = 0;
    int dec = 1;
 
    for(;len >= 0; --len)
    {
        if(str[len] >= '0' && str[len] <= '9')
        {
            num += dec * (str[len] - '0');
            dec *= 10;
        }        
        else if((len == 0) && (str[len] == '-' || str[len] == '+'))
            num = (str[0] == '-') ? -num : num;
        else
        {
            //numero invalido, entao 0
            return 0;
        }
    }    

    return num;
}

int main(int argc, char **argv)
{
    using namespace std;

    int num = myAtoi("23");

    cout << num << endl;
    cout << myAtoi("-10") + myAtoi("5") << endl;

    return 0;
}

A função myAtoi processa a string e retorna o int que ela representa, note que a função processa a string do final para o começo para facilitar os cálculos. No loop principal primeiramente é verificado se o caracter atual é um número (ou seja, tem que estar entre ‘0′ e ‘9′). Se for um número, a variável num é atualizada com a formula: dec * (str[len] – ‘0′). O código str[len] – ‘0′ calcula qual o valor do número atual (da mesma forma que foi feita na conversão de letras para caixa alta). Achado o número, multiplicamos ele por dec, que armazena qual a casa decimal que ele se encontra (unidade, dezena, centena, etc), e por fim o valor é somado com o número atual. Depois disso a casa decimal é atualizada (em dec *= 10).

Caso o caracter não seja um número, é verificado se a string contêm o sinal + ou – na primeira posição, caso sim, o sinal do número é atualizado de maneira adequada. Caso o caractere em questão não seja número ou sinal, a função retorna 0. O correto seria retornar algum erro, mas para não complicar o exemplo ficamos com o zero.

Na biblioteca padrão existem as funções atoi e strtol que fazem conversões de strings para números.

A conversão de int para string fica como lição de casa, uma boa leitura para se aprender mais é o artigo sobre tipos básicos do Caloni: Básico do básico: tipos.


Strings em C

Dezembro 11, 2008

Um item que vejo muitos novatos na linguagem tendo dificuldades são as strings em C, por isso resolvi escrever alguns posts e tentar resolver muitas das duvidas que vejo por ai.

Para começo de conversa strings em C praticamente não existem, o compilador tem apenas uma vaga noção do que é uma string, pois elas são uma convenção onde um vetor de caracteres terminado com 0 (zero) representa uma string. Sendo assim, podemos criar uma string usando:


#include <stdio.h>

int main(int, char **)
{
    char ola[4];

    ola[0] = 'o';
    ola[1] = 'l';
    ola[2] = 'a';
    ola[3] = 0;

    printf(ola);

    return 0;
}

Este código simplesmente imprime “ola” na tela, mas note que ao inicializar ola colocamos o caracter ‘\ 0′ (que nada mais é que o numero 0) no final do array, ele é necessário pois é a única forma do printf saber onde termina a string. Agora escrever esse código toda vez que precisarmos criar uma string é bem chato, por isso, uma das poucas coisas que o compilador C sabe sobre strings é:

#include <stdio.h>
 
int main(int, char **)
{
    char ola[] = "ola";

    printf(ola);

    return 0;
}

Este código é idêntico ao anterior, mas escrito de uma maneira bem mais simples. Note que o compilador reconhece a string “ola” e declara um array de 4 chars e já inicializa ela com a string “ola” (incluindo o ). Também podemos escrever:

#include <stdio.h>

int main(int, char **)
{
    const char *ola = "ola";

    printf(ola);

    return 0;

}

Este código já é um pouco diferente do anterior, aqui não criamos um vetor de caracteres, e sim um ponteiro para uma região de memória constante do tipo char, trocando por miúdos, criamos um ponteiro que aponta para string. Mas onde diabos foi parar a string? O compilador alocou um trecho de memória constante na seção de dados do código (uma variável global). Por isso usamos const, pois esta string não deve ser modificada pelo código.

Contando Caracteres

Agora que já sabemos como uma string funciona em C, vamos fazer uma função para contar quantos caracteres uma string tem, assim podemos ver como o 0 no final dela é usado:

#include <stdio.h>

int contaChar(const char *str)
{
    int i = 0;

    for(;str[i] != 0; ++i);

    return i;
}

int main(int, char **)
{
    char ola[] = "ola";

    printf("A string %s possui %d caracteres\n", ola, contaChar(ola));

    return 0;
}

 
Note que sempre que precisamos saber quantos caracteres uma string possui, precisamos percorrer toda a string, isso pode trazer problemas de performance em algumas aplicações, e isto ocorre com quase todas as operações com string.

 

Outro detalhe, não é preciso criar a função contaChar, quando precisar saber o tamanho de uma string basta usar a strlen, que é declarada no string.h:

#include <stdio.h>
#include <string.h>

int main(int, char **)
{
    char ola[] = "ola";

    printf("A string %s possui %d caracteres\n", ola, strlen(ola));

    return 0;
}

Neste mesmo arquivo existem varias outras funções para se trabalhar com strings, é recomendável dar uma olhada na documentação antes de escrever a sua própria função para verificar se ela já não existe (alias, isso é recomendável para qualquer coisa, não apenas strings).
 

Comparando Strings

Agora chegamos ao ponto onde a maioria tem dificuldades, como saber se uma string é igual a outra? A idéia inicial é escrever:

#include <stdio.h>

int main(int, char **)
{
    char ola[] = "ola";
    char ola2[] = "ola";

    if(ola == ola2)
        printf("Iguais");
    else
        printf("Nao sao iguais");

    return 0;
}

Se você entendeu tudo até aqui já deve imaginar qual vai ser a saída do programa acima, se não, tente ler novamente e execute o programa. Muitos ficam surpresos ao ver o programa imprimir “Não são iguais”. Isto ocorre porque o if esta na verdade comparando dois ponteiros (lembre-se, arrays sem índice são ponteiros).

Então como fazer para saber se duas strings são iguais? É necessário comparar todos os caracteres das strings:

#include <stdio.h>

bool saoIguais(const char *s1, const char *s2)
{        
    for(int i = 0;s1[i] == s2[i]; ++i)
    {                
        if(s1[i] == 0)
            return true;
    }
    return false;
}

int main(int, char **)
{
    char ola[] = "ola";
    char ola2[] = "ola";

    if(saoIguais(ola, ola2))
        printf("Iguais");
    else
        printf("Nao sao iguais");

    return 0;
}

Agora já sabemos como identificar se duas strings são iguais ou não, um pouco trabalhoso, mas é o único jeito. Para simplificar um pouco a vida, a biblioteca padrão já vem com uma função de comparação de strings chamada strcmp, que funciona de maneira similar a função saoIguais, a diferença é que essa função não verifica apenas se as duas strings são iguais, ela compara as duas, retornando 0 quando são iguais, -1 quando a primeira string vem antes da segunda, ou 1 caso contrário:

#include <stdio.h>
#include <string.h>

int main(int, char **)
{
    char ola[] = "ola";
    char ola2[] = "ola";

    if(strcmp(ola, ola2) == 0)
        printf("Iguais\n");
    else if(strcmp(ola, ola2) != 0)
        printf("Nao sao iguais");

    return 0;
}

Pronto, agora já temos uma maneira simples de verificar se duas strings são iguais ou não. 

No próximo post, vamos dar uma olhada em mais algumas operações com strings em C.


C++ Type Casting – 3ª e ultima parte

Novembro 5, 2008

No post anterior vimos como realizar as operações de casting usando os novos operadores do C++, neste post vamos ver como realizar casting com os smart pointers da Boost , estes operadores podem ser usados com o shared_ptr e o intrusive_ptr.

Mas primeiramente, porque existem operadores de cast específicos da Boost? Pelo fato de que os operadores do C++ não estão preparados para lidar com smart pointers e contagem de referências, dessa forma, caso se utilize um operador padrão do C++ a contagem de referência do objeto pode se tornar invalida, resultando em um objeto sendo destruído mais de uma vez. Exemplo:


enum Eventos_e
{
    TIRO,
    ABRIR_PORTA
};

class Objeto
{
    public:
        virtual void Evento(int tipo, void *param) = 0;
        virtual void Atualizar()=0;
};

class Monstro: public Objeto
{
    public:
        virtual void Evento(int tipo, void *param);
        virtual void Atualizar();
       
    private:
        int m_energia;
};

Com base na hierarquia acima, vamos criar alguns objetos e fazer um cast:


int main(int, char **)
{
    using namespace boost;

    shared_ptr<Objeto> obj(new Monstro());
   
    shared_ptr<Monstro> monstro(static_cast<Monstro *>(obj.get()));   
}

Repare que na linha onde é feito o cast é usado o método get do shared_ptr para se obter uma referência ao objeto que obj armazena, depois é feito o cast e o ponteiro resultante é utilizado para inicializar o ponteiro “monstro”, note que monstro foi inicializado com um ponteiro comum, não com outro shared_ptr, ou seja, para o shared_ptr “monstro” ninguém até o momento gerenciava esse ponteiro, então ele vai simplesmente começar uma nova contagem de referências, dessa forma, quando a função terminar de executar, ambos os smart pointers vão destruir o objeto Monstro, resultando em um comportamento indefinido.

Para evitar esses problemas foram criados os casts a seguir:

Cada um dos casts acima deve ser usado nas mesmas situações dos casts do C++, a diferença é que eles devem ser apenas usados quando os objetos envolvidos são gerenciados por smart pointers.

No caso do exemplo anterior, o código correto é listado abaixo:


int main(int, char **)
{
    using namespace boost;

    shared_ptr<Objeto> obj(new Monstro());
   
    shared_ptr<Monstro> monstro(static_pointer_cast<Monstro>(obj)));   
}

Note que as operações de cast da boost são idênticas as do C++, a diferença é o nome do operador usado.

As operações de cast da Boost devem ser sempre usadas quando é necessário fazer casts entre ponteiros gerenciados por smart pointers da mesma, nos outros casos, deve-se utilizar os operadores do C++.


C++ Type Casting – 2ª Parte

Setembro 18, 2008

No post anterior vimos como funciona o casting que o C++ herdou da linguagem C, agora vamos ver como funcionam os novos operadores de casting do C++

static_cast

O static_cast é o mais simples de todos, ele faz em partes o trabalho do cast do C, mas com algumas restrições que veremos na sequência. Baseando-se no exemplo do post anterior onde tínhamos as classes Objeto, Monstro e Tiro pode-se construir:

void CriaExplosao(int potencia);
void ExplodeTiro(Objeto *obj)
{
Tiro *tiro = static_cast<Tiro *>(obj);

CriaExplosao(tiro->Potencia());
}

Por alguma obra do destino que não vem ao caso (ou por falta de criatividade do autor com exemplos) a função ExplodeTiro recebe como parâmetro uma referência para Objeto, e não tiro. Por se tratar da função ExplodeTiro sabemos e confiamos friamente que os programadores sempre vão usar ela passando como parâmetro uma classe Tiro.

O static_cast não faz verificação nenhuma para checar se o Objeto passado como parâmetro é da classe Tiro ou não, apenas ajusta algum endereço se necessário, nada mais. Agora vamos imaginar que um novo programador começou a trabalhar no projeto e sem saber das consequências escreveu:

void func()
{
Monstro m;

ExplodeTiro(&m);
}

O que vai acontecer? Se da ultima vez o cachorro da vizinha latiu, dessa vez é provável que ele exploda, quem sabe o cãozinho que faleceu segundo relataram alguns leitores não ressuscite com essa técnica? Piadas a parte, o comportamento é totalmente imprevisível nesse caso.

A vantagem de se usar o static_cast é que o compilador faz algumas verificações antes de usá-lo:

Tiro tiro;
Monstro *m = static_cast<Monstro *>(&tiro);

O código acima vai dar erros, porque Tiro e Monstro são classes que não podem ser convertidas entre si. Se fosse utilizado um cast do C o compilador aceitaria tudo sem problemas.

Outra vantagem do static_cast é que ele somente permite seu uso com tipos definidos:

class X;
class Y;
void proc(X *x)
{
Y *p = static_cast<Y *>(x);
}

Esse é o mesmo exemplo do artigo do Bjarne listado nas referências abaixo, o interessante desse código é que ele não compila, pois o compilador ainda não conhece a estrutura das classes X e Y, evitando assim a geração de código que pode fazer alguem perder a noite com um bug misterioso.

O static_cast também evita erros como remoção de const por acidente, acesso a membros privados, etc. Dessa forma ele impõe uma série de restrições ao código, minimizando erros que poderiam passar despercebidos.

dynamic_cast

Este cast é utilizado quando é preciso fazer um cast de uma classe base para uma classe derivada. No caso da função ExplodeTiro acima, podemos melhora-la trocando o static_cast pelo dynamic_cast:

void ExplodeTiro(Objeto &obj)
{
Tiro *tiro = dynamic_cast<Tiro *>(&obj);
if(tiro == NULL)
return;

CriaExplosao(tiro->Potencia());
}

Note que caso a conversão não seja possível, o dynamic_cast retorna NULL, dando oportunidade ao programador de verificar se o tipo é valido ou não. No caso da função acima, decidimos simplesmente parar a execução da função quando não é possível fazer a conversão.

O dynamic_cast é o cast mais complexo em termos computacionais do C++, pois ele precisa realizar algumas buscas pela hierarquia de objetos e isso algumas vezes pode levar um tempo precioso, sendo assim, deve-se usá-lo com moderação.

Agora no dia a dia, se o programador tiver certeza absoluta de que o tipo a fazer cast é da classe derivada, não existe problema algum em usar o static_cast ao invés do dynamic_cast (a não ser o risco de fazer coisa errada). Existem códigos onde ao invés dos casts, existe uma template onde na versão debug do código é usado um dynamic_cast com assert, na versão release é usado apenas um static_cast.

Outro detalhe do dynamic_cast é que para ele funcionar é necessário ligar a geração de RTTI (Run Time Type Information – Informação de Tipos em Tempo de Execução), que é opcional em alguns compiladores (Visual Studio por exemplo). Pode ser que algum compilador esse item não seja opcional, mas para o dynamic_cast funcionar ele tem que existir. No caso do visual, se alguem tenta usar o dynamic_cast sem RTTI ele vai gera um erro em tempo de compilação.

reinterpret_cast

Este cast é utilizado quando queremos converter um tipo para um outro tipo não relacionado, como por exemplo de char* para int*. Ele é apenas uma indicação ao compilador de que você sabe o que esta fazendo. Note que ele também pode ser usado para tipos que ainda não foram definidos, mas atenção, o reinterpret_cast diferentemente do dynamic_cast não navega pela hierarquia de classes. Exemplos:

void ExplodeTiro(Objeto *obj)
{
Tiro *tiro = reinterpret_cast<Tiro *>(obj);

CriaExplosao(tiro->Potencia());
}

No exemplo acima o código compila, mas a não ser que a intenção seja realmente essa, esse código somente vai dar problemas. No primeiro cast da classe Objeto para Tiro quando o reinterpret_cast é usado os ponteiros não são ajustados. O ajuste de ponteiros é feito quando trocamos ponteiros de uma classe filha para uma classe pai ou vice versa, isso depende da implementação, mas baseado no exemplo, o ponteiro para Tiro pode não possuir exatamente o mesmo endereço do ponteiro para Objeto (mesmo se tratando do mesmo objeto). Geralmente em hierarquias simples (sem herança múltipla ou herança virtual) o endereço é sempre o mesmo.

O reinterpret_cast é recomendado apenas para operações onde se deseja converter um tipo básico para ponteiro e vice-versa:

void func()
{
int i = 5;
char *p = reinterpret_cast<char *>(i);

++p;

i = reinterpret_cast<int>(p);
}

Outro detalhe é que o reinterpret_cast também preserva constness de tipos assim como o static_cast.

const_cast

O ultimo tipo e o mais simples de todos é o const_cast, este cast simplesmente tira o const de um tipo:

void ExplodeTiro(const Objeto *obj)
{
Tiro *tiro = const_cast<Tiro *>(static_cast<const Tiro *>(obj));

CriaExplosao(tiro->Potencia());

//um cast tipico apenas para remover o cont
Objeto *o = const_cast<Objeto *>(obj);
}

Note que foi usado um static_cast que converte para “const Tiro *”, pois se fosse usado dentro do static_cast apenas “Tiro *” o compilador iria reclamar, então com o resultado dele é aplicado o const_cast, que remove o const do tipo. As pessoas reclamam que é muito código para pouca coisa, mas em troca disso temos um código mais seguro, além de ficar bem explicito quais são as intenções do programador e para terminar, essas operações não implicam custos extras em relação a um cast tradicional do C, então o uníco motivo para não usar é preguiça na hora de digitar.

Sobre o exemplo acima, o ideal é mudar o método potência e transforma-lo em um método const, mas vamos supor que isso não fosse possível por alguma outra obra do acaso :) .

Conclusões

Os casts do C++ trazem mais segurança ao programador e ajudam a evitar erros de conversões invalidas. Apesar de eles serem muito mais longos que um cast normal (em termos de tamanho de código a se digitar) é vantajoso utiliza-los em vista das vantagens que eles trazem.

Caso esteja em duvida sobre qual cast utilizar, utilize o static_cast, se o compilador reclamar:

  • Se for um const sendo removido, estude o caso e veja se é realmente necessário, caso sim, utilize o const_cast.
  • Se for algum tipo incompleto, adicione o include apropriado para definir o tipo ou veja se é necessário trocar por um reinterpret_cast.

Referência

New Casts Revisited – Bjarne Stroustrup: a base de todo esse post, contém vários exemplos de como utilizar e de como bagunçar tudo com os casts do C++.


C++ Type Casting – 1ª Parte

Agosto 27, 2008

A linguagem C++ (assim como a linguagem C) possui operações de casting de tipos (Type Casting) que é uma operação em que se converte uma expressão de um determinado tipo para outro tipo, essas conversões podem ser feitas de duas maneiras: implícita e explicita.

Conversão Implícita

Nesse caso, o programador não precisa fazer nada, pois a conversão de dados é feita automaticamente sem que ele precise usar qualquer instrução extra:


int main(int, char **)
{
	char a = 5;
	int i = a;
}

No código acima não existe nada demais, certo? O único detalhe é que quando fazemos i = a, essa não é uma atribuição comum, o compilador precisa gerar código extra para converter o char para int. Esta conversão é feita de maneira implícita e silenciosa pelo compilador porque qualquer valor de char pode ser armazena em um int. Já um int, não pode ter todos os seus valores armazenados em um char. Se pegarmos como exemplo um compilador 32 bits, onde o char geralmente tem 8 bits, os seus valores vão de -128 a 127, já um int (32 bits) pode possuir valores de -2147483648 a 2147483647.

Se fizermos ao contrário:


int main(int, char **)
{
	int i = 2147483647;
	char a = i;

}

Alguns compiladores vão gerar avisos com o código acima (no caso do Visual, tem que mudar o Warning level de 3 para 4). O problema do código acima é que quando atribuímos um valor de i para a, a variável char não tem como armazenar todos os valores de um inteiro, nesse caso, pode haver uma perda de dados.

Conversão Explicita

Se você estiver usando o compilador com warnings no máximo, é possível evitar o aviso usando o código abaixo:


int main(int, char **)
{
	int i = 2147483647;
	char a = (char)i;

}

O código gerado acima vai ser idêntico ao anterior, o cast nesse caso não faz nada em relação ao código gerado, você apenas esta sinalizando: “Cale a boca compilador que eu sei o que estou fazendo”. O compilador entende o seu recado e não lhe da mais aviso nenhum.

Agora vamos provocar a ira do compilador:


#include <string>
#include <vector>

int main(int, char **)
{
	using namespace std;

	vector<int> v;
	string *str = &v;

	*str = "abc";

    return 0;
}

O código acima deu um belo de um erro…, isso ocorre porque o C++ tenta evitar que o programador faça coisas erradas, vamos tentar consertar a situação:


#include <string>
#include <vector>

int main(int, char **)
{
	using namespace std;

	vector<int> v;
	string *str = (string*)&v;

    *str = "abc";

    return 0;
}

Agora novamente o compilador ficou quieto, mas o que aconteceu? Quase nada, quando colocamos o (string*) estamos dizendo ao compilador: “Ei, o valor da expressão &v é o endereço de uma string”. O compilador simplesmente aceita (afinal nós sabemos o que estamos fazendo) e copia o valor para o ponteiro str. Atenção ao detalhe “copia o endereço”, pois lembre-se que ponteiros no fundo são apenas um unsigned int com propriedades quase magicas. Note também que não foi feito nada quanto a conversão de tipo.

O que vai acontecer quando esse código for executado? Pode acontecer qualquer coisa:

  • O código pode simplesmente funcionar por alguma obra do acaso;
  • Dar uma falha de segmentação;
  • Seu computador explodir;
  • O cachorro do vizinho passar mal

No meu caso, deu um baita erro de assertion failed. Segunda a especificação, esse tipo de operação tem como resultado “undefined” ou seja, indefinido. Mas note que o problema não esta no casting, ele é totalmente valido, o problema ocorre quando tentamos jogar “abc” dentro de str. Nesse caso o operador de atribuição da string vai ser chamado, mas o ponteiro this dele ao invés de apontar para um objeto string, vai estar apontando para o vector!

Exemplo: Sistema de Eventos

Se isso causa apenas problemas, porque existe então? Bom, isso permite fazer algumas coisas interessantes… vamos imaginar que você esta criando um jogo, e nesse jogo você possui uma lista de objetos, que podem ser qualquer coisa: um monstro, um tiro, uma porta, chave, etc.

Todos esses objetos possuem uma classe base em comum, chamada Objeto:


enum Eventos_e
{
	TIRO,
	ABRIR_PORTA
};

class Objeto
{
	public:
		virtual void Evento(int tipo, void *param) = 0;
		virtual void Atualizar()=0;
};

class Monstro: public Objeto
{
	public:
		virtual void Evento(int tipo, void *param);
		virtual void Atualizar();

	private:
		int m_energia;
};

class Tiro: public Objeto
{
	public:
		virutal void Evento(int tipo, void *param);
		virtual void Atualizar();

		int Potencia();

	private:
		Objeto *MovimentarEColidir();
};

Agora, vamos imaginar que o monstro estava andando e levou um tiro de laser. Quando qualquer coisa acontece no nosso jogo, chamamos o método Evento e passamos as informações necessárias. O método atualizar é chamado o tempo todo para que os objetos se atualizem (como por exemplo movimentar o tiro, andar com o monstro, etc).

Vamos agora implementar o método Atualizar do tiro:


void Tiro::Atualizar()
{
	Objeto *obj = MovimentarEColidir();
	if(obj)
	{
		obj->Evento(TIRO, this);
	}
}

O método é bem simples, vamos assumir que o método MovimentarEColidir atualiza a posição do tiro e verifica se ele bateu com algo, caso isso ocorra, o objeto com o qual ele colidiu é retornado. Quando isso ocorre, o tiro envia um evento ao objeto avisando-o do ocorrido, dessa forma não precisamos poluir a interface da classe Objeto com todo o tipo de evento que pode acontecer (tiro, abrir, usar item, etc). Por outro lado, esse tipo de código é complicado de debugar e não é muito intuitivo, além de permitir que o programador faça muitas burradas sem aviso algum do compilador.

Agora vamos ver como fica o código do Monstro:


void Monstro::Evento(int tipo, void *param)
{
	if(tipo == TIRO)
	{
		Tiro *tiro = (Tiro *)param;

		energia -= tiro->potencia();
	}
}

Note que quando recebemos um evento do TIRO, simplesmente fazemos um cast do ponteiro e diminuímos a energia do monstro com base na potencia do tiro, extremamente simples.

Esse tipo de código funciona muito bem em jogos (apesar de existirem outras técnicas para se conseguir o mesmo feito, algumas melhores, outras nem tanto). Esta técnica serve para outros sistemas também, um exemplo é a API do Windows que usa essa técnica para fazer a comunicação entre os componentes da GUI.

No próximo post, vamos ver como usar os operadores de casting do C++.


Boost Intrusive Pointer (intrusive_ptr)

Agosto 8, 2008

Para ver o artigo anterior da série: Boost Shared Array e Scoped Array

Finalmente chegamos na ultima classe de smart pointer da Boost. O intrusive_ptr é semelhante ao shared_ptr, mas a diferença é que ao invés de alocar um objeto para controlar as referências, ele usa o próprio objeto que ele armazena para esse fim.

Dessa forma, o objeto apontado por ele precisa prover métodos ou funções que cuidem da contagem de referência, por isso o nome de intrusive (ou intruso), porque é necessário modificar o objeto que ele vai armazenar para que se possa usa-lo com o intrusive_ptr, devido a essa forma de implementação, já notamos aqui que não podemos usar o intrusive_ptr com qualquer tipo de objeto como fazíamos com o shared_ptr.

Quando um intrusive_ptr precisa incrementar as referências, ele chama a função intrusive_ptr_add_ref (passando como parâmetro o ponteiro do objeto). No processo inverso, quando é preciso decrementar uma referência, ele invoca a função intrusive_ptr_release, que fica responsável por decrementar as referências e destruir o objeto quando o contador chega a zero.

Vamos então modificar a classe Person para que ela possa ser usada com o intrusive_ptr:


#include <boost/intrusive_ptr.hpp>
#include <string>

class Person
{
    public:
        inline Person():
            m_RefCount(0)
        {
        }

        inline Person(const Person &p):
            m_Name(p.m_Name),
            m_RefCount(0)
        {
        }

        inline const Person &operator=(const Person &p)
        {
            m_Name = p.m_Name;

            return(*this);
        }

        inline void SetName(const std::string &name)
        {
            m_Name = name;
        }

    private:
        inline void AddRef()
        {
            ++m_RefCount;
        }

        inline void DecRef()
        {
            assert(m_RefCount > 0);

            --m_RefCount;
            if(m_RefCount == 0)
                delete this;
        }

    private:
        std::string m_Name;
        unsigned int m_RefCount;

        friend inline void intrusive_ptr_add_ref(Person *p);
        friend inline void intrusive_ptr_release(Person *p);
};

inline void intrusive_ptr_add_ref(Person *p)
{
    p->AddRef();
}

inline void intrusive_ptr_release(Person *p)
{
    p->DecRef();
}

int main(int argc, char **argv)
{
    using namespace boost;

    intrusive_ptr<Person> p(new Person());

    p->SetName("BCS");

    return(0);
}

A classe Person engordou um bocado agora, então vamos por partes:

  1. Foi adicionado o atributo m_RefCount que usamos como contador de referência, usamos um unsigned porque não tem o menor sentido um objeto com contagem negativa.
  2. Tive que adicionar um construtor para inicializar o contador com zero.
  3. Foi preciso também adicionar um construtor de cópia e um operador de atribuição para que o contador seja inicializado e copiado corretamente. Note que no operador de cópia, simplesmente não alteramos o contador.
  4. Criamos os métodos AddRef e DecRef, que incrementam e decrementam o contador, respectivamente. O DecRef possui um assert apenas para tentarmos pegar algum estado inconsistente. Note que o objeto é suicida, se o contador chegou a zero, ele se mata (delete this).
  5. Declaramos as funções intrusive_ptr_add_ref e intrusive_ptr_release para a classe Person, note que elas simplesmente invocam os AddRef e o DecRef. Outro detalhe foi a declaração delas como friend da classe Person, desse modo podemos deixar o acesso ao contador de referências privado e evitar uso equivocado desses métodos.

Vantagens

A primeira vista o intrusive_ptr parece ser uma versão mala do shared_ptr, mas existem muitos casos onde temos um grande numero de objetos e não podemos arcar com o peso de ter uma alocação extra para controle de referencias, nessas horas o intrusive_ptr se torna bem atraente.

Outra vantagem do intrusive_ptr é que podemos usar ele com objetos que já possuem algum controle por contagem de referencia, como por exemplo objetos COM do Windows, podemos simplesmente criar as funções intrusive_ptr_add_ref e intrusive_ptr_release adequadas.

Desvantagens

A maior desvantagem que vejo é que não existe uma implementação do weak_ptr, então é necessário tomar certo cuidado com referências circulares.

Outra desvantagem (que na verdade incomoda apenas quando vamos usa-lo pela primeira vez) é que ele se torna mais trabalhoso que o shared_ptr devido ao código extra para controle de referencias, mas novamente, isso acaba se pagando com as vantagens que temos.

No próximo post vamos (ainda não acabou) ver as funções de casting para smart pointer.