O que seria um conjunto de hacks bacanas de pré-processador (compatíveis com ANSI C89 / ISO C90) que permitem algum tipo de orientação de objeto feia (mas utilizável) em C?
Eu estou familiarizado com algumas linguagens orientadas a objetos diferentes, portanto, não responda com respostas como "Aprenda C ++!". Eu li " Programação Orientada a Objetos com ANSI C " (cuidado: formato PDF ) e várias outras soluções interessantes, mas estou principalmente interessado na sua :-)!
Consulte também Você pode escrever código orientado a objetos em C?
Respostas:
O C Object System (COS) parece promissor (ainda está na versão alfa). Ele tenta manter o mínimo de conceitos disponíveis por questões de simplicidade e flexibilidade: programação uniforme orientada a objetos, incluindo classes abertas, metaclasses, metaclasses de propriedades, genéricos, métodos múltiplos, delegação, propriedade, exceções, contratos e fechamentos. Há um rascunho (PDF) que o descreve.
A exceção em C é uma implementação C89 do TRY-CATCH-FINALLY encontrada em outros idiomas OO. Ele vem com um testinguite e alguns exemplos.
Ambos por Laurent Deniau, que está trabalhando muito em OOP em C .
fonte
Eu desaconselharia o uso do pré-processador (ab) para tentar tornar a sintaxe C mais parecida com a de outra linguagem mais orientada a objetos. No nível mais básico, você apenas usa estruturas simples como objetos e as passa por ponteiros:
Para obter coisas como herança e polimorfismo, você precisa trabalhar um pouco mais. Você pode fazer herança manual fazendo com que o primeiro membro de uma estrutura seja uma instância da superclasse e, em seguida, você pode usar ponteiros para basear e derivar classes livremente:
Para obter polimorfismo (ou seja, funções virtuais), use ponteiros de função e, opcionalmente, tabelas de ponteiros de função, também conhecidas como tabelas virtuais ou vtables:
E é assim que se faz polimorfismo em C. Não é bonito, mas faz o trabalho. Existem alguns problemas complicados que envolvem a conversão de ponteiros entre as classes base e derivada, que são seguros desde que a classe base seja o primeiro membro da classe derivada. A herança múltipla é muito mais difícil - nesse caso, para alternar entre classes base diferentes da primeira, você precisa ajustar manualmente seus ponteiros com base nos deslocamentos adequados, o que é realmente complicado e propenso a erros.
Outra coisa (complicada) que você pode fazer é alterar o tipo dinâmico de um objeto em tempo de execução! Você acabou de atribuir a ele um novo ponteiro de tabela. Você pode alterar seletivamente algumas das funções virtuais enquanto mantém outras, criando novos tipos híbridos. Apenas tenha cuidado para criar uma nova vtable em vez de modificar a vtable global, caso contrário você afetará acidentalmente todos os objetos de um determinado tipo.
fonte
struct derived {struct base super;};
é óbvia para adivinhar como funciona, pois pela ordem dos bytes está correta.Certa vez, trabalhei com uma biblioteca C que foi implementada de uma maneira que me pareceu bastante elegante. Eles haviam escrito, em C, uma maneira de definir objetos e depois herdá-los para que fossem tão extensíveis quanto um objeto C ++. A ideia básica era esta:
Herdar é difícil de descrever, mas basicamente era o seguinte:
Em outro arquivo:
Então você poderia ter uma van criada na memória e sendo usada por código que sabia apenas sobre veículos:
Funcionou lindamente, e os arquivos .h definiram exatamente o que você deveria poder fazer com cada objeto.
fonte
A área de trabalho do GNOME para Linux é escrita em C orientado a objetos e possui um modelo de objeto chamado " GObject ", que suporta propriedades, herança, polimorfismo, além de outras vantagens como referências, manipulação de eventos (chamados "sinais"), tempo de execução digitação, dados privados etc.
Inclui hacks de pré-processador para fazer coisas como digitar na hierarquia de classes, etc. Aqui está um exemplo de classe que escrevi para o GNOME (coisas como gchar são typedefs):
Origem da Classe
Cabeçalho da classe
Dentro da estrutura GObject, há um número inteiro GType que é usado como um número mágico para o sistema de digitação dinâmica do GLib (você pode converter toda a estrutura em um "GType" para descobrir seu tipo).
fonte
Eu costumava fazer esse tipo de coisa em C, antes de saber o que era OOP.
A seguir, é apresentado um exemplo, que implementa um buffer de dados que cresce sob demanda, considerando um tamanho mínimo, um incremento e um tamanho máximo. Essa implementação específica foi baseada em "elemento", ou seja, foi projetada para permitir uma coleção semelhante a uma lista de qualquer tipo C, não apenas um buffer de bytes de tamanho variável.
A idéia é que o objeto seja instanciado usando xxx_crt () e excluído usando xxx_dlt (). Cada um dos métodos "membro" usa um ponteiro de tipo específico para operar.
Eu implementei uma lista vinculada, buffer cíclico e várias outras coisas dessa maneira.
Devo confessar que nunca pensei em como implementar herança com essa abordagem. Imagino que alguma mistura daquilo oferecido por Kieveli possa ser um bom caminho.
dtb.c:
dtb.h
PS: vint era simplesmente um typedef do int - usei-o para me lembrar que seu comprimento era variável de plataforma para plataforma (para portar).
fonte
Ligeiramente fora de tópico, mas o compilador C ++ original, Cfront , compilou C ++ em C e depois no assembler.
Preservado aqui .
fonte
Se você pensa em métodos chamados objetos como métodos estáticos que passam um '
this
' implícito para a função, isso pode facilitar o pensamento OO em C.Por exemplo:
torna-se:
Ou algo assim.
fonte
string->length(s);
O ffmpeg (um kit de ferramentas para processamento de vídeo) é escrito em C direto (e linguagem assembly), mas usando um estilo orientado a objetos. Está cheio de estruturas com ponteiros de função. Há um conjunto de funções de fábrica que inicializam as estruturas com os ponteiros de "método" apropriados.
fonte
Se você realmente pensa catefully, mesmo padrão de uso da biblioteca C OOP - considerar
FILE *
como um exemplo:fopen()
inicializa umFILE *
objeto, e você usá-lo usar métodos membrosfscanf()
,fprintf()
,fread()
,fwrite()
e outros, e, eventualmente, finalizá-lo comfclose()
.Você também pode seguir a maneira pseudo-objetiva-C, que também não é difícil:
Usar:
Isto é o que pode resultar de um código Objective-C como este, se um tradutor de Objective-C-to-C bastante antigo for usado:
fonte
__attribute__((constructor))
fazvoid __meta_Foo_init(void) __attribute__((constructor))
?popen(3)
também retorna aFILE *
para outro exemplo.Acho que o que Adam Rosenfield postou é a maneira correta de fazer POO em C. Gostaria de acrescentar que o que ele mostra é a implementação do objeto. Em outras palavras, a implementação real seria colocada no
.c
arquivo, enquanto a interface seria colocada no.h
arquivo de cabeçalho . Por exemplo, usando o exemplo de macaco acima:A interface ficaria assim:
Você pode ver no
.h
arquivo de interface que você está apenas definindo protótipos. Você pode então compilar a parte ".c
arquivo" da implementação em uma biblioteca estática ou dinâmica. Isso cria encapsulamento e você também pode alterar a implementação à vontade. O usuário do seu objeto não precisa saber quase nada sobre sua implementação. Isso também enfatiza o design geral do objeto.É minha convicção pessoal que oop é uma maneira de conceituar sua estrutura e reutilização de código e realmente não tem nada a ver com as outras coisas adicionadas ao c ++, como sobrecarga ou modelos. Sim, esses são recursos úteis muito agradáveis, mas não representam o que realmente é a programação orientada a objetos.
fonte
typedef struct Monkey {} Monkey;
Qual é o objetivo de digitar após a criação?struct _monkey
é simplesmente um protótipo. A definição de tipo real é definida no arquivo de implementação (o arquivo .c). Isso cria o efeito de encapsulamento e permite que o desenvolvedor da API redefina a estrutura do macaco no futuro sem modificar a API. Os usuários da API precisam se preocupar apenas com os métodos reais. O designer da API cuida da implementação, incluindo como o objeto / estrutura é organizado. Portanto, os detalhes do objeto / estrutura estão ocultos do usuário (um tipo opaco).int getCount(ObjectType obj)
etc, se optar por definir a estrutura no arquivo de implementação.Minha recomendação: seja simples. Um dos maiores problemas que tenho é a manutenção de software mais antigo (às vezes com mais de 10 anos). Se o código não for simples, pode ser difícil. Sim, pode-se escrever POO muito útil com polimorfismo em C, mas pode ser difícil de ler.
Prefiro objetos simples que encapsulem algumas funcionalidades bem definidas. Um ótimo exemplo disso é o GLIB2 , por exemplo, uma tabela de hash:
As chaves são:
fonte
Se eu fosse escrever OOP no CI, provavelmente iria com um design pseudo- Pimpl . Em vez de passar ponteiros para estruturas, você acaba passando ponteiros para ponteiros para estruturas. Isso torna o conteúdo opaco e facilita o polimorfismo e a herança.
O verdadeiro problema com OOP em C é o que acontece quando as variáveis saem do escopo. Não há destruidores gerados pelo compilador e isso pode causar problemas. As macros podem ajudar, mas sempre será feio de se ver.
fonte
if
instruções e liberando-as no final. Por exemploif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }
Outra maneira de programar em um estilo orientado a objeto com C é usar um gerador de código que transforma uma linguagem específica de domínio em C. Como é feito com TypeScript e JavaScript para trazer OOP para js.
fonte
Resultado:
Aqui está um show do que é programação OO com C.
É C real, puro, sem macros de pré-processador. Temos herança, polimorfismo e encapsulamento de dados (incluindo dados privados de classes ou objetos). Não há chance para o equivalente qualificado do qualificador, ou seja, os dados privados também são privados na cadeia de herança. Mas isso não é um inconveniente, porque não acho necessário.
CPolygon
não é instanciado porque apenas o usamos para manipular objetos da cadeia de herança que possuem aspectos em comum, mas diferentes implementações (Polimorfismo).fonte
@ Adam Rosenfield tem uma explicação muito boa de como obter OOP com C
Além disso, eu recomendo que você leia
1) pjsip
Uma biblioteca C muito boa para VoIP. Você pode aprender como ele alcança OOP através de estruturas e tabelas de ponteiros de funções
2) Tempo de execução do iOS
Aprenda como o iOS Runtime potencializa o Objetivo C. Ele alcança OOP por meio de um apontador, meta classe
fonte
Para mim, a orientação a objetos em C deve ter os seguintes recursos:
Encapsulamento e ocultação de dados (pode ser alcançado usando structs / ponteiros opacos)
Herança e suporte ao polimorfismo (a herança única pode ser obtida usando estruturas - verifique se a base abstrata não é instanciada)
Funcionalidade de construtor e destruidor (não é fácil de alcançar)
Verificação de tipo (pelo menos para tipos definidos pelo usuário, pois C não impõe nenhum)
Contagem de referência (ou algo para implementar RAII )
Suporte limitado para tratamento de exceções (setjmp e longjmp)
Além disso, ele deve se basear nas especificações ANSI / ISO e não na funcionalidade específica do compilador.
fonte
Veja http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html . Se nada mais for ler a documentação é uma experiência esclarecedora.
fonte
Estou um pouco atrasado para a festa aqui, mas gosto de evitar os dois extremos da macro - muitos ou muitos códigos ofuscam, mas algumas macros óbvias podem facilitar o desenvolvimento e a leitura do código OOP:
Eu acho que isso tem um bom equilíbrio, e os erros gerados (pelo menos nas opções padrão do gcc 6.3) para alguns dos erros mais prováveis são úteis, em vez de confusos. A questão toda é melhorar a produtividade do programador, não?
fonte
Se você precisar escrever um pequeno código, tente o seguinte: https://github.com/fulminati/class-framework
fonte
Também estou trabalhando nisso com base em uma solução macro. Portanto, é apenas para os mais corajosos, eu acho ;-) Mas já é muito bom, e já estou trabalhando em alguns projetos. Funciona para que você defina primeiro um arquivo de cabeçalho separado para cada classe. Como isso:
Para implementar a classe, você cria um arquivo de cabeçalho para ele e um arquivo C em que implementa os métodos:
No cabeçalho que você criou para a classe, você inclui outros cabeçalhos necessários e define tipos etc. relacionados à classe. No cabeçalho da classe e no arquivo C, você inclui o arquivo de especificação da classe (consulte o primeiro exemplo de código) e uma macro X. Essas macros X ( 1 , 2 , 3 etc.) expandirão o código para as estruturas de classe reais e outras declarações.
Para herdar uma classe
#define SUPER supername
e adicionarsupername__define \
como a primeira linha na definição de classe. Ambos devem estar lá. Também há suporte a JSON, sinais, classes abstratas etc.Para criar um objeto, basta usar
W_NEW(classname, .x=1, .y=2,...)
. A inicialização é baseada na inicialização da estrutura introduzida no C11. Funciona bem e tudo o que não está listado é definido como zero.Para chamar um método, use
W_CALL(o,method)(1,2,3)
. Parece uma chamada de função de ordem superior, mas é apenas uma macro. Ele se expande, o((o)->klass->method(o,1,2,3))
que é um truque muito bom.Consulte a documentação e o próprio código .
Como a estrutura precisa de algum código padrão, escrevi um script Perl (wobject) que faz o trabalho. Se você usar isso, basta escrever
e criará o arquivo de especificação da classe, o cabeçalho da classe e um arquivo C, que inclui
Point_impl.c
onde você implementa a classe. Isso economiza bastante trabalho, se você tiver muitas classes simples, mas ainda assim tudo estiver em C. wobject é um scanner muito simples, baseado em expressões regulares, fácil de se adaptar a necessidades específicas ou de ser reescrito do zero.fonte
O projeto de código aberto Dynace faz exatamente isso. Está em https://github.com/blakemcbride/Dynace
fonte
Você pode experimentar o COOP , uma estrutura amigável para programadores para OOP em C, que apresenta classes, exceções, polimorfismo e gerenciamento de memória (importante para o código incorporado). É uma sintaxe relativamente leve, veja o tutorial no Wiki lá.
fonte