sexta-feira, 8 de junho de 2007

Um novo conceito em visualização de imagens

Essa é a nova proposta do Photosynth, um projeto originado na Seadragon Software, recém adquirida pela Microsoft.

Com ele você pode ver fotos por qualquer ângulo, dar zoom em imagens com a mesma velocidade, independente do tamanho, agrupar fotos inter-relacionadas e muito mais.

Eu assisti ao vídeo, instalei o plug-in no meu Firefox e experimentei um pouco o Photosynth. Bem interessante por sinal.

Sinto que hoje há uma forte tendência em criar interfaces gráficas em três dimensões (3D), de forma que possamos manipular objetos de formas cada vez mais próximas às do mundo real.

Aliás, essa é a proposta ideal das interfaces com o usuário. Ser intuitiva ao ponto de não ser preciso criar muitas metáforas e abstrações para operar o computador. Manipular janelas e clicar em botões quadrados e menus deslizantes está cada vez mais "fora de moda". Sabemos, com certeza, que essa não é a maneira ideal - apesar de termos nos acostumado ao longo do tempo - de interagir com  o PC. Mas se será como em Minority Report ou em Matrix, só um futuro, cada vez mais próximo, irá dizer.

quinta-feira, 7 de junho de 2007

Utilitário: Unlocker

Quem não já tentou excluir um arquivo e o arquivo não pôde ser excluído porque "estava sendo utilizado" ? Você fechou o programa que estava usando o arquivo, mas ele continua lá, bloqueado, sem que você possa mandá-lo pros confins da lixeira ou mesmo eliminá-lo diretamente (Shift+Del).

Pra resolver problemas deste tipo que existe o Unlocker, um freeware que se integra ao menu de contexto, permitindo que você destrave o arquivo, ou pasta, e possa fazer com ele o que bem desejar.

Screenshot do Unlocker

Dica C++: Passagem correta de objetos em funções e métodos

Existem diferentes formas de se lidar com alocação de objetos e sua passagem para métodos e funções. A escolha pela maneira correta pode evitar ter que lidar com diversos problemas e, consequentemente, com bugs e dificuldades de depuração.

Vamos analisar alguns casos e ver como é possível ter implementações diferentes para um mesmo tipo de necessidade, e como determinado tipo de implementação pode interferir no resultado final, em aspectos de confiabilidade, segurança e performance.

Funções que recebem objetos por valor

Suponha que haja necessidade da criação de uma função qualquer que tome um objeto como parâmetro à fim de obter alguns de seus valores. Chamarei esta função de FuncaoXYZ:

void FuncaoXYZ(...);

Para a passagem de um objeto de uma classe qualquer (que chamarei de TClasseQualquer) por valor, temos algumas possibilidades para esta simples função:

(1) void FuncaoXYZ(TClasseQualquer objeto);

(2) void FuncaoXYZ(const TClasseQualquer objeto);

(3) void FuncaoXYZ(const TClasseQualquer *objeto);

(4) void FuncaoXYZ(const TClasseQualquer &objeto);

Neste caso, vemos como é importante o conhecimento da linguagem de programação na qual se está implementando. Este conhecimento influencia diretamente na escolha de implementação ótima e outra passível de problemas.

Nas funções acima, tanto (1) como (2) fazem uma cópia do objeto e trabalham com esta cópia dentro da função. Ou seja, a cada vez que a função é chamada ela precisa criar uma nova cópia do objeto. Isso pode consumir tempo e recursos, se o objeto for "grande" e esta operação for executada repetidas vezes, como dentro de um laço de repetição, por exemplo.

Em (1) também não há garantia de que a função não fará modificações no objeto, pois o mesmo não foi passado como uma constante. Mesmo que neste tipo de implementação ela esteja criando uma cópia do objeto original e por isso não irá modificá-lo, esse é um tipo de comportamento indesejado, que deveria ser evitado.

Em (3) não há certeza de que o objeto passado não é nulo (NULL). Logo, a função sempre deverá fazer o teste antes de acessar o objeto recebido, sob pena de acessar um endereço de memória inválido. Esse tipo de situação deve ser evitado, sendo portanto desaconselhável a passagem de objetos por ponteiro quando só desejamos acessar seus valores, sem alterá-lo.

Assim, em (4), chegamos a uma implementação que nos livra de ter que checar se o objeto recebido é nulo, por não ser um ponteiro, que garante que ele não poderá ser modificado dentro da função, por ser uma constante, e que não criará uma cópia do objeto recebido, já que foi criado um apelido (uma referência, ou seja &) para o objeto original.

Logo, utilizando a notação "const T&" (onde T é um tipo qualquer) para funções que recebem um objeto por valor, garantimos um determinado comportamento e assim saberemos, se algo der errado, que diversos tipos de condições (como as descritas acima) não irão ocorrer.

Se tomarmos esse tipo de implementação como regra, livramos o software desses problemas.

Métodos que recebem de objetos por valor

Como métodos são funções dentro de classes, podemos levar em consideração a mesma regra adotada para funções vista acima (notação "const T&").

Porém, há ainda um aspecto importante que devemos analisar: O que garante que o método, em sua implementação, não irá modificar os atributos da classe, caso desejado ?

Em C++ você dá esta garantia adicionando "const" após a declaração do método:

void FuncaoXYZ(
  const TClasseQualquer &objeto) const;

Assim, o compilador emite um erro se método tentar alterar qualquer atributo da classe.

Funções que recebem objetos por referência

Quando há a necessidade de alterar um objeto recebido por parâmetro, dentro de uma função, podemos fazê-lo declarando de uma das seguintes formas:

(1) void FuncaoXYZ(TClasseQualquer *objeto);

(2) void FuncaoXYZ(TClasseQualquer &objeto);

Como foi visto, fica claro a escolha de (2) pois não temos que testar se o objeto recebido é nulo. E também fica mais difícil de pensar em deletar o objeto da memória, já que não é um ponteiro.

Métodos que recebem objetos por referência

A mesma notação para funções pode ser aplicada, podendo ser adicionado o uso de const quando aplicável (quando não se desejar mudar o valor de algum atributo da classe dentro da implementação do método).

Outros usos

Como a notação de referência é útil para nos poupar dos aborrecimentos trazidos pelos ponteiros, podemos utilizá-la sempre que possível. Mesmo utilizando bibliotecas de terceiros que possuam a notação de ponteiro (*), podemos adaptar seu uso.

Por exemplo:

void TMinhaClasse::ReimplementacaoDoMetodoASDFG(
  const TAlgumaClasse *ponteiro)
{
    #ifdef _DEBUG // PRE-CONDICAO
    assert( ponteiro != NULL );  
    #endif

    const TAlgumaClasse &referencia = ponteiro;

    // trabalha com "referencia" ao inves
    // de "ponteiro"
    // ...

}

Ao invés de utilizar o ponteiro real, criamos uma referência para ele.

Vale lembrar que se a função aceitasse nulo (NULL) como um valor válido, não deveríamos substituir o ponteiro pela notação de referência. (Ou até poderíamos fazê-lo se o valor nulo pudesse ser tratado em um bloco de código separado (com um if, por exemplo), sendo um caso a parte, e o resto da função utilizasse o objeto como sendo não-nulo.)

Conclusão

A utilização correta dos recursos da linguagem pode trazer diversos benefícios ao software escrito, desde melhoria de performance até diminuição dos índices de erro e tempo de depuração.

No caso do uso de objetos em funções e métodos, evita problemas indesejáveis relacionados à alocação memória e acesso irrestrito à informações.

Sempre que aplicável, devemos utilizar parâmetros constantes e referências ao invés de ponteiros.