Murilo :P

C++, Computação, Programação, Web e afins :)

Como programar em C Orientado a Objetos

with 14 comments

Olá, hoje eu vou dar um tempo na série “Coisas simples de se fazer em C++ que alguns ainda complicam” e irei falar sobre uma experiência minha tentando alguma maneira de programar em C orientado a objetos.

Sei que muitos vão pensar “pra quê isso?” ou dizer que é péssimo fazer isso ou coisa parecida, mas o intuito desse post é outro: é mostrar o que é possível fazer ou até mesmo enxergar alguma utilidade nisso. Nesse blog, procuro colocar coisas diferentes, curiosidades sobre programação e linguagens, porque acho que conteúdo “normal” já existem em muitos lugares na grande rede.

O que me instigou a fazer isso foi um post no CODARE “C: Escondendo o conteúdo de structs com tipos incompletos” de autoria do Thiago Santos, no qual ele cita sobre usar com orientação a objetos em C.

Li, achei interessante e quis, vamos dizer assim, dar um cara mais parecida de orientação a objetos ao que ele fez.

O primeiro passo foi pensar em como funcionam os famosos objetos.
Uma classe em uma linguagem orientada a objetos geralmente tem 2 tipos de elementos:

  • Elementos de classe: elementos (funções e variáveis estáticas) que só são criados uma vez, todos os objetos da classe tem acesso ao mesmo elemento.
  • Elementos de instância (ou objeto): elementos (atributos) que são criadas para cada objeto instanciado.

Ou seja:

class Animal
{
        int age;
        std::string specie;
        static int count;
public:
        int birthday();
};

No exemplo acima, age e specie são elementos de instância, ou seja, cada objeto têm o seus próprios. Já count e birthday() são de classe, pois todos os objetos dessa classe utilizarão o mesmo. O que ocorre no caso de funções é que na chamada da função, um ponteiro do objeto chamador é passado para a função. Isso faz com que não precise a cada instância de objeto criar uma nova função já que elas fazem a mesma coisa.

Algo como:

Animal animal, animal2;

//lembrando que é APENAS uma ilustração do que acontece
//não é assim que é realmente implementado mas é a mesma idéia
//uma função só para todos os objetos de uma classe

animal.birthday(); //vtable::animal::birthday(&animal);
animal2.birthday(); //vtable::animal::birthday(&animal2);

A função birthday() é a mesma para as duas chamadas, o que muda é o ponteiro para os dados de cada objeto.

A minha implementação seguiu a idéia do Thiago Santos: uma classe person que tem os atributos name e age funções para instanciar, imprimir o nome e idade e deletar o objeto.

Vamos então dar uma olhada no nosso headerperson.h:

#ifndef PERSON_H__
#define PERSON_H__

//Incomplete type declaration
typedef struct person_private person_private;

typedef struct person {
	//"private" data.
	person_private* data;

	//"class" functions
	void (*free)();
	void (*print)();
} person;

//instatiate a new person
person* new_person(const char*, int);

//pointer to the actual person in the context
person* __actual_person;

//sets the actual person
person* _(person* obj);

#endif

Nosso header contém a declaração incompleta de person_private leia aqui para saber o porquê, a definição da nossa “classe” person, as declarações das funções new_person() e _() e um ponteiro __actual_person.

Nossa “classe” person contém um ponteiro data para os dados que não poderão ser acessíveis através do objeto (name, age) além de ponteiros para funções (que serão nossos “métodos”).

A função new_person simplesmente instancia um objeto do tipo person.

O ponteiro __actual_person irá funcionar como o ponteiro passado para as funções de classe. Através do ponteiro saberemos qual objeto chamou a função.

A função _() seta o ponteiro __actual_person para que as funções sejam corretamente chamadas. Nota: não é thread safe. (hehehe)

Vamos ao nosso person.c:

#include "person.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//private data... visible only by the functions below
struct person_private
{
	char* name;
	int age;
};

//a "manual destructor"
void free_person()
{
	free(__actual_person->data->name);
	free(__actual_person->data);
	free(__actual_person);
       
        puts("Person sucessfuly freed!\nBye");
}

//prints 
void print_person()
{
	printf("%s -: %d\n", __actual_person->data->name,
		 __actual_person->data->age);
}

person* new_person(const char* name, int age)
{

	//Allocate the object	
	person* new = (person*)malloc(sizeof(person));
	new->data = (person_private*)malloc(sizeof(person_private));
	
	//Initialize the data
	new->data->name = (char*)malloc(strlen(name) * sizeof(char) + 1);
	strcpy(new->data->name, name);

	new->data->age = age;

	//Set the functions pointers
	new->print = print_person;
	new->free = free_person;
	
	return new;
}

//must call the objects with this function
person* _(person* obj)
{
	__actual_person = obj;
	return obj;
}

O que temos aí é a função _() que seta o objeto atual (__actual_person) para que possa ser utilizado a função com o objeto certo, a função new_person que funciona como nosso construtor, as outras duas funções que são os “métodos da classe” print e free e a definição da estrutura person_private que contém os atributos da classe.
Os comentários ajudam no resto :).

Vamos então ver o main.c que é o nosso teste:

#include <stdio.h>
#include "person.h"

int main()
{
	//Instantiates 2 persons
	person* person1 = new_person("Murilo", 21);
	person* person2 = new_person("Rovane", 47);
	
	//print
	_(person1)->print();
	_(person2)->print();
	
	//free
	_(person1)->free();
	_(person2)->free();
}

Eis o resultado:

murilo@blacksheep:~/programacao/cobject$ gcc main.c person.c
murilo@blacksheep:~/programacao/cobject$ ./a.out
Murilo -: 21
Rovane -: 47
Person sucessfuly freed!
Bye
Person sucessfuly freed!
Bye

Nota: deve-se usar sempre a função _() para utilizar o objeto.

Fiquei pensando se tem como fazer uma espécie de gerador de classes nesse formato no próprio C (com macros por exemplo). Vou procurar saber se existe algo a respeito, são 04h40min da mardugada não estou mais com boas idéias.

Qualquer sugestão, bug, idéia, reclamação, estamos aí!

Advertisements

Written by Murilo Adriano

5 de August de 2009 at 04:50

Posted in C/C++, Programação

Tagged with , , , , ,

14 Responses

Subscribe to comments with RSS.

  1. Murilo,

    Vi o anuncio que você fez na comunidade C/C++ Brasil deste post e resolvi dar uma olhada. Bem interessante sua abordagem. Gostaria de dar duas sugestões:

    – Declarando __actual_person em person.h, todos os arquivos que incluirem esse header file irão definir desnecessariamente essa variável. Já que ela é usada somente pelas funções definidas em person.c, acredito ser melhor definir __actual_person nesse arquivo como static.

    – Ao invés de declarar um ponteiro para cada função de person dentro de person, você poderia criar uma estrutura com uma tabela de funçoes de person e fazer cada instância de person apontar para essa tabela. Fazendo isso, gasta-se uma indireção a mais para chamar o “método”, mas em compensação não se aumenta o tamanho de person com o aumento de funções.

    Finalizando, gostei bastante do seu blog. Parabéns pela iniciativa.

    Alcindo

    5 de August de 2009 at 12:14

  2. Olá Alcindo, obrigado pelas sugestões.
    Realmente __actual_person deve ser definido em person.c e essa idéia da struct com as funções também é boa pois teríamos apenas o overhead de dereferência mas pouparíamos o espaço ao instanciarmos objetos.

    Valeu mesmo!

    Murilo Adriano

    5 de August de 2009 at 16:04

  3. Dará problemas se dois objetos tentarem chamar um método ao mesmo tempo (threading), já que o objeto-atual é um só para a classe (static).

    daniel

    6 de August de 2009 at 14:50

    • A função _() seta o ponteiro __actual_person para que as funções sejam corretamente chamadas. Nota: não é thread safe. (hehehe)

      Sim Daniel, como disse anteriormente, a função _() não é threading safe, pois não era do scopo do artigo falar sobre condições de corrida e threading.
      Para fazer essa função ser threading safe seria muito simples, bastaria usar um mutex e bloquear a função _() até que o primeiro que chamou termine de chamar a função (“método da classe”) corretamente.

      Murilo Adriano

      6 de August de 2009 at 15:11

      • Já tinha esse quote sobre ser thread-safe? :þ

        Sim, botar uma lock com o acquire e release na _() ia eliminar esse problema 🙂

        Mas parabéns, não sei se é original o método mas de qualquer forma é bem interessante e é mais prático que outros que já vi.

        daniel

        7 de October de 2009 at 01:09

  4. Olá,

    Muito bom o Post! porém que achas de trocar o bloco

    – new->data->name = (char*)malloc(strlen(name) * sizeof(char) + 1);
    – strcpy(new->data->name, name);

    Por

    + new->data->name = strdup (name);

    Muito Bom!!
    []s

    Jorge Pereira

    10 de August de 2009 at 14:24

    • Valeu Jorge!!
      Realmente muito melhor usar strdup() do que malloc() + strcpy()

      Abraços!

      Murilo Adriano

      10 de August de 2009 at 22:28

  5. Oi Murilo,

    Bem legal a idéia, mas como você mesmo já viu, existe o problema de não ser thread-safe. Muitas bibliotecas fazem algo parecido, mas resolvem o problema de thread-safeness poluindo um pouco o código, algo assim:

    person1->print(person1)

    Por outro lado não precisa mais de usar o _(…).

    []’s and keep coding!

    Thiago Marcos P. Santos

    16 de August de 2009 at 20:14

    • Realmente!
      Essa é a abordagem mais comum se tratando de OO com C.
      Valeu Thiago, abraço!

      Murilo Adriano

      17 de August de 2009 at 08:45

  6. Olá, Murilo.

    Eu particularmente tenho restrições em tentar simular a POO implementada em C++ em C, principalmente por questões sintáticas. Prefiro outro approach, que é a de criar funções que trabalhem com objetos opacos. O importante, na minha opinião, é manter o princípio da programação orientada ao objeto e não tentar simular a sintaxe de outra linguagem visto que isso pode dificultar demais a clareza do código-fonte.

    De qualquer maneira, seu approach é muito interessante e gostei muito da forma como você apresentou o tema. Parabéns pelo artigo.

    Ronaldo Faria Lima

    27 de August de 2009 at 11:24

    • Valeu Ronaldo!
      Depois do post li sobre as mais variadas técnicas de “orientar o C a objetos”.
      Tem umas bem interessantes. 🙂

      Murilo Adriano

      27 de August de 2009 at 13:13

  7. Aonde esta o objeto, exemplo botao fechar tipo statusbar ou algo parecido. no seu exemplo so apresce numeros e caracteres em uma tela preta. ainda nao entendi aonde esta o objeto.

    andre

    4 de September de 2011 at 13:41

    • Olá Andre!
      “Objeto” é uma estrutura de dados que possui atributos (variáveis) e operações (funções/métodos).
      A aplicação é uma aplicação para linha de comando mesmo (por isso a tela preta com os caracteres).
      Você pode ler um pouco mais sobre orientação a objetos nesse link do Wikipedia.

      Murilo Adriano

      5 de September de 2011 at 15:14

  8. Ajudou bastante ein ! vlw murilo.

    Lucas Tomkiel

    3 de October de 2011 at 19:24


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: