Você pode escrever código orientado a objects em C?

Você pode escrever código orientado a objects em C? Especialmente em relação ao polymorphism.


Veja também a questão do Stack Overflow . Orientação do object em C.

   

Sim. Na verdade, Axel Schreiner fornece gratuitamente o livro “Programação orientada a objects em ANSI-C”, que cobre o assunto completamente.

Já que você está falando sobre polymorphism, então sim, você pode, nós estávamos fazendo esse tipo de coisa anos antes do surgimento do C ++.

Basicamente, você usa uma struct para manter os dados e uma lista de pointers de function para apontar para as funções relevantes para esses dados.

Assim, em uma class de comunicação, você teria uma chamada aberta, de leitura, de gravação e de fechamento, que seria mantida como quatro pointers de function na estrutura, junto com os dados de um object, algo como:

 typedef struct { int (*open)(void *self, char *fspec); int (*close)(void *self); int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz); int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz); // And data goes here. } tCommClass; tCommClass commRs232; commRs232.open = &rs232Open; : : commRs232.write = &rs232Write; tCommClass commTcp; commTcp.open = &tcpOpen; : : commTcp.write = &tcpWrite; 

Obviamente, esses segmentos de código acima estariam, na verdade, em um “construtor”, como rs232Init() .

Quando você ‘herda’ dessa class, basta alterar os pointers para apontar para suas próprias funções. Todo mundo que chamou essas funções faria isso através dos pointers de function, dando-lhe o seu polymorphism:

 int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000"); 

Mais ou menos como uma vtable manual.

Você pode até ter classs virtuais definindo os pointers como NULL – o comportamento seria um pouco diferente do C ++ (um dump principal em tempo de execução, em vez de um erro no tempo de compilation).

Aqui está um pedaço de código de exemplo que demonstra isso. Primeiro a estrutura de classs de nível superior:

 #include  // The top-level class. typedef struct sCommClass { int (*open)(struct sCommClass *self, char *fspec); } tCommClass; 

Então nós temos as funções para a ‘subclass’ TCP:

 // Function for the TCP 'class'. static int tcpOpen (tCommClass *tcp, char *fspec) { printf ("Opening TCP: %s\n", fspec); return 0; } static int tcpInit (tCommClass *tcp) { tcp->open = &tcpOpen; return 0; } 

E o HTTP também:

 // Function for the HTTP 'class'. static int httpOpen (tCommClass *http, char *fspec) { printf ("Opening HTTP: %s\n", fspec); return 0; } static int httpInit (tCommClass *http) { http->open = &httpOpen; return 0; } 

E finalmente um programa de teste para mostrá-lo em ação:

 // Test program. int main (void) { int status; tCommClass commTcp, commHttp; // Same 'base' class but initialised to different sub-classs. tcpInit (&commTcp); httpInit (&commHttp); // Called in exactly the same manner. status = (commTcp.open)(&commTcp, "bigiron.box.com:5000"); status = (commHttp.open)(&commHttp, "http://www.microsoft.com"); return 0; } 

Isso produz a saída:

 Opening TCP: bigiron.box.com:5000 Opening HTTP: http://www.microsoft.com 

assim você pode ver que as diferentes funções estão sendo chamadas, dependendo da subclass.

Os namespaces geralmente são feitos fazendo:

 stack_push(thing *) 

ao invés de

 stack::push(thing *) 

Para fazer uma estrutura C em algo como uma class C ++, você pode transformar:

 class stack { public: stack(); void push(thing *); thing * pop(); static int this_is_here_as_an_example_only; private: ... }; 

Para dentro

 struct stack { struct stack_type * my_type; // Put the stuff that you put after private: here }; struct stack_type { void (* construct)(struct stack * this); // This takes uninitialized memory struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it void (*push)(struct stack * this, thing * t); // Pushing t onto this stack thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it int this_is_here_as_an_example_only; }Stack = { .construct = stack_construct, .operator_new = stack_operator_new, .push = stack_push, .pop = stack_pop }; // All of these functions are assumed to be defined somewhere else 

E fazer:

 struct stack * st = Stack.operator_new(); // Make a new stack if (!st) { // Do something about it } else { // You can use the stack stack_push(st, thing0); // This is a non-virtual call Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push st->my_type.push(st, thing2); // This is a virtual call } 

Eu não fiz o destrutor ou deletei, mas segue o mesmo padrão.

this_is_here_as_an_example_only é como uma variável de class estática – compartilhada entre todas as instâncias de um tipo. Todos os methods são realmente estáticos, exceto que alguns aceitam isso *

Acredito que além de ser útil por si só, implementar OOP em C é uma excelente maneira de aprender OOP e entender seu funcionamento interno. A experiência de muitos programadores mostrou que, para usar uma técnica com eficiência e confiança, um programador deve entender como os conceitos subjacentes são implementados. Emulando classs, inheritance e polymorphism em C ensina apenas isso.

Para responder a pergunta original, aqui estão alguns resources que ensinam como fazer OOP em C:

A postagem no blog da EmbeddedGurus.com “Programação baseada em object em C” mostra como implementar classs e inheritance única no C portátil: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

Nota de Aplicação “” C + “- Programação Orientada a Objetos em C” mostra como implementar classs, inheritance única e binding tardia (polymorphism) em C usando macros de pré-processador: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , o código de exemplo está disponível em http://www.state-machine.com/resources/cplus_3.0.zip

Eu já vi isso feito. Eu não recomendaria isso. Inicialmente, o C ++ começou dessa forma como um pré-processador que produzia o código C como uma etapa intermediária.

Essencialmente, o que você acaba fazendo é criar uma tabela de despacho para todos os seus methods onde você armazena suas referências de function. Derivar uma class implicaria copiar esta tabela de dispatch e replace as inputs que você deseja replace, com seus novos “methods” tendo que chamar o método original se ele quiser invocar o método base. Eventualmente, você acaba reescrevendo o C ++.

Claro que isso é possível. É isso que o GObject , o framework no qual todo o GTK + e o GNOME é baseado, faz.

A sub-biblioteca Stary FILE é um excelente exemplo de como criar abstração, encapsulamento e modularidade em C. não adulterado.

Herança e polymorphism – os outros aspectos freqüentemente considerados essenciais para POO – não necessariamente fornecem os ganhos de produtividade que prometem e argumentos razoáveis foram feitos para que eles possam realmente impedir o desenvolvimento e pensar sobre o domínio do problema.

Exemplo trivial com Animal e Dog: Você espelha o mecanismo vtable do C ++ (em grande parte, de qualquer maneira). Você também separa a alocação e a instanciação (Animal_Alloc, Animal_New), portanto não chamamos malloc () várias vezes. Também devemos explicitamente passar o ponteiro ao redor.

Se você fizesse funções não virtuais, isso seria trival. Você simplesmente não os adiciona à vtable e as funções estáticas não requerem this ponteiro. A inheritance múltipla geralmente requer múltiplos vtables para resolver ambiguidades.

Além disso, você deve poder usar setjmp / longjmp para fazer o tratamento de exceções.

 struct Animal_Vtable{ typedef void (*Walk_Fun)(struct Animal *a_This); typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This); Walk_Fun Walk; Dtor_Fun Dtor; }; struct Animal{ Animal_Vtable vtable; char *Name; }; struct Dog{ Animal_Vtable vtable; char *Name; // Mirror member variables for easy access char *Type; }; void Animal_Walk(struct Animal *a_This){ printf("Animal (%s) walking\n", a_This->Name); } struct Animal* Animal_Dtor(struct Animal *a_This){ printf("animal::dtor\n"); return a_This; } Animal *Animal_Alloc(){ return (Animal*)malloc(sizeof(Animal)); } Animal *Animal_New(Animal *a_Animal){ a_Animal->vtable.Walk = Animal_Walk; a_Animal->vtable.Dtor = Animal_Dtor; a_Animal->Name = "Anonymous"; return a_Animal; } void Animal_Free(Animal *a_This){ a_This->vtable.Dtor(a_This); free(a_This); } void Dog_Walk(struct Dog *a_This){ printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name); } Dog* Dog_Dtor(struct Dog *a_This){ // Explicit call to parent destructor Animal_Dtor((Animal*)a_This); printf("dog::dtor\n"); return a_This; } Dog *Dog_Alloc(){ return (Dog*)malloc(sizeof(Dog)); } Dog *Dog_New(Dog *a_Dog){ // Explict call to parent constructor Animal_New((Animal*)a_Dog); a_Dog->Type = "Dog type"; a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk; a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor; return a_Dog; } int main(int argc, char **argv){ /* Base class: Animal *a_Animal = Animal_New(Animal_Alloc()); */ Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc()); a_Animal->vtable.Walk(a_Animal); Animal_Free(a_Animal); } 

PS. Isso é testado em um compilador C ++, mas deve ser fácil fazê-lo funcionar em um compilador C.

Confira o GObject . É para ser OO em C e uma implementação do que você está procurando. Se você realmente quer OO, vá com C ++ ou alguma outra linguagem OOP. O GObject pode ser muito difícil de se trabalhar se você estiver acostumado a lidar com linguagens OO, mas, como qualquer coisa, você se acostumará com as convenções e o stream.

Isso foi interessante para ler. Eu tenho ponderado a mesma pergunta, e os benefícios de pensar sobre isso são:

  • Tentar imaginar como implementar conceitos de OOP em uma linguagem não-OOP me ajuda a entender os pontos fortes da linguagem OOp (no meu caso, C ++). Isso ajuda a me dar um melhor julgamento sobre o uso de C ou C ++ para um determinado tipo de aplicativo – onde os benefícios de um superam o outro.

  • Na minha navegação na web por informações e opiniões sobre isso, encontrei um autor que estava escrevendo código para um processador incorporado e só tinha um compilador C disponível: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Criando-Foundation-Classes-Part-1

No seu caso, analisar e adaptar os conceitos de OOP na linguagem C era uma busca válida. Parece que ele estava aberto para sacrificar alguns conceitos de OOP devido ao impacto de sobrecarga de desempenho resultante da tentativa de implementá-los em C.

A lição que aprendi é que, sim, isso pode ser feito até certo ponto e, sim, existem algumas boas razões para tentar.

No final, a máquina está girando os bits do ponteiro da pilha, fazendo com que o contador de programa pule e calcule as operações de access à memory. Do ponto de vista da eficiência, quanto menos desses cálculos forem feitos pelo seu programa, melhor … mas às vezes temos que pagar esse imposto simplesmente para que possamos organizar nosso programa de uma forma que o torne menos suscetível a erros humanos. O compilador de linguagem OOP se esforça para otimizar ambos os aspectos. O programador tem que ser muito mais cuidadoso na implementação desses conceitos em uma linguagem como C.

Você pode achar útil consultar a documentação da Apple para o conjunto de APIs da Core Foundation. É uma API C pura, mas muitos dos tipos são vinculados a equivalentes de object Objective-C.

Você também pode achar útil examinar o design do próprio Objective-C. É um pouco diferente do C ++ em que o sistema de objects é definido em termos de funções C, por exemplo, objc_msg_send para chamar um método em um object. O compilador traduz a syntax de colchetes entre essas chamadas de function, então você não precisa saber disso, mas, considerando a sua pergunta, você pode achar útil aprender como funciona sob o capô.

Existem várias técnicas que podem ser usadas. O mais importante é mais como dividir o projeto. Usamos uma interface em nosso projeto que é declarada em um arquivo .h e a implementação do object em um arquivo .c. A parte importante é que todos os módulos que incluem o arquivo .h vêem apenas um object como void * , e o arquivo .c é o único módulo que conhece os componentes internos da estrutura.

Algo parecido com isto para uma class que chamamos de FOO como exemplo:

No arquivo .h

 #ifndef FOO_H_ #define FOO_H_ ... typedef struct FOO_type FOO_type; /* That's all the rest of the program knows about FOO */ /* Declaration of accessors, functions */ FOO_type *FOO_new(void); void FOO_free(FOO_type *this); ... void FOO_dosomething(FOO_type *this, param ...): char *FOO_getName(FOO_type *this, etc); #endif 

O arquivo de implementação C será algo parecido.

 #include  ... #include "FOO.h" struct FOO_type { whatever... }; FOO_type *FOO_new(void) { FOO_type *this = calloc(1, sizeof (FOO_type)); ... FOO_dosomething(this, ); return this; } 

Então eu dou o ponteiro explicitamente para um object para cada function desse módulo. Um compilador C ++ faz isso implicitamente, e em C nós o escrevemos explicitamente.

Eu realmente uso this em meus programas, para ter certeza de que meu programa não compila em C ++, e tem a boa propriedade de estar em outra cor no meu editor de realce de syntax.

Os campos do FOO_struct podem ser modificados em um módulo e outro módulo não precisa ser recompilado para ser ainda utilizável.

Com esse estilo eu já lidei com grande parte das vantagens do OOP (encapsulamento de dados). Usando pointers de function, é até mesmo fácil implementar algo como inheritance, mas honestamente, é raramente muito útil.

Objeto orientado C, pode ser feito, eu vi esse tipo de código em produção na Coréia, e foi o monstro mais horrível que eu vi em anos (isso foi como no ano passado (2007) que eu vi o código). Então, sim, isso pode ser feito, e sim, as pessoas já fizeram isso antes, e ainda o fazem mesmo nos dias de hoje. Mas eu recomendo C ++ ou Objective-C, ambos são idiomas nascidos de C, com o objective de fornecer orientação a objects com diferentes paradigmas.

Se você está convencido de que uma abordagem OOP é superior para o problema que você está tentando resolver, por que você estaria tentando resolvê-lo com uma linguagem não-OOP? Parece que você está usando a ferramenta errada para o trabalho. Use C ++ ou alguma outra linguagem de variante C orientada a object.

Se você está perguntando porque você está começando a codificar em um projeto grande já existente escrito em C, então você não deve tentar forçar seus próprios paradigmas OOP (ou de outra pessoa) na infra-estrutura do projeto. Siga as orientações que já estão presentes no projeto. Em geral, as APIs e bibliotecas e módulos isolados irão percorrer um longo caminho no sentido de ter um design OOP- ish limpo.

Se, depois de tudo isso, você estiver realmente pronto para fazer OOP C, leia isto (PDF).

Sim você pode. As pessoas estavam escrevendo C orientado a objects antes do C ++ ou do Objective-C entrar em cena. C ++ e Objective-C foram, em partes, tentativas de pegar alguns dos conceitos OO usados ​​em C e formalizá-los como parte da linguagem.

Aqui está um programa muito simples que mostra como você pode fazer algo que pareça / é uma chamada de método (existem maneiras melhores de fazer isso. Esta é apenas uma prova de que a linguagem suporta os conceitos):

 #include struct foobarbaz{ int one; int two; int three; int (*exampleMethod)(int, int); }; int addTwoNumbers(int a, int b){ return a+b; } int main() { // Define the function pointer int (*pointerToFunction)(int, int) = addTwoNumbers; // Let's make sure we can call the pointer int test = (*pointerToFunction)(12,12); printf ("test: %u \n", test); // Now, define an instance of our struct // and add some default values. struct foobarbaz fbb; fbb.one = 1; fbb.two = 2; fbb.three = 3; // Now add a "method" fbb.exampleMethod = addTwoNumbers; // Try calling the method int test2 = fbb.exampleMethod(13,36); printf ("test2: %u \n", test2); printf("\nDone\n"); return 0; } 

Claro, isso não será tão bonito quanto usar uma linguagem com suporte embutido. Eu até escrevi “assembler orientado a object”.

Um pequeno código OOC para adicionar:

 #include  struct Node { int somevar; }; void print() { printf("Hello from an object-oriented C method!"); }; struct Tree { struct Node * NIL; void (*FPprint)(void); struct Node *root; struct Node NIL_t; } TreeA = {&TreeA.NIL_t,print}; int main() { struct Tree TreeB; TreeB = TreeA; TreeB.FPprint(); return 0; } 

Você pode fingir usando pointers de function e, na verdade, acho que é teoricamente possível compilar programas C ++ em C.

No entanto, raramente faz sentido forçar um paradigma em uma linguagem, em vez de escolher uma linguagem que use um paradigma.

Eu tenho cavado isso por um ano:

Como o sistema GObject é difícil de usar com o C puro, eu tentei escrever algumas boas macros para facilitar o estilo OO com C.

 #include "OOStd.h" CLASS(Animal) { char *name; STATIC(Animal); vFn talk; }; static int Animal_load(Animal *THIS,void *name) { THIS->name = name; return 0; } ASM(Animal, Animal_load, NULL, NULL, NULL) CLASS_EX(Cat,Animal) { STATIC_EX(Cat, Animal); }; static void Meow(Animal *THIS){ printf("Meow!My name is %s!\n", THIS->name); } static int Cat_loadSt(StAnimal *THIS, void *PARAM){ THIS->talk = (void *)Meow; return 0; } ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL) CLASS_EX(Dog,Animal){ STATIC_EX(Dog, Animal); }; static void Woof(Animal *THIS){ printf("Woof!My name is %s!\n", THIS->name); } static int Dog_loadSt(StAnimal *THIS, void *PARAM) { THIS->talk = (void *)Woof; return 0; } ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL) int main(){ Animal *animals[4000]; StAnimal *f; int i = 0; for (i=0; i<4000; i++) { if(i%2==0) animals[i] = NEW(Dog,"Jack"); else animals[i] = NEW(Cat,"Lily"); }; f = ST(animals[0]); for(i=0; i<4000; ++i) { f->talk(animals[i]); } for (i=0; i<4000; ++i) { DELETE0(animals[i]); } return 0; } 

Aqui está o meu site do projeto (eu não tenho tempo suficiente para escrever doc., No entanto, o documento em chinês é muito melhor).

OOC-GCC

Há um exemplo de inheritance usando C no discurso de 1996 de Jim Larson, ministrado no Seminário de Preparação de Almoço da Seção 312 aqui: Alto e Baixo Nível C.

Quais artigos ou livros são bons para usar conceitos OOP em C?

As Interfaces e Implementações C de Dave Hanson são excelentes no encapsulamento e nomenclatura e muito boas no uso de pointers de function. Dave não tenta simular inheritance.

OOP é apenas um paradigma que coloca os dados como mais importantes do que o código nos programas. OOP não é uma linguagem. Então, como o C simples é uma linguagem simples, o OOP em C simples também é simples.

Uma coisa que você pode querer fazer é olhar para a implementação do kit de ferramentas Xt para o X Window . Claro que está ficando muito tempo no dente, mas muitas das estruturas utilizadas foram projetadas para trabalhar em uma forma OO dentro do tradicional C. Geralmente isso significa adicionar uma camada extra de indireção aqui e ali e projetar estruturas para se sobreporem umas às outras.

Você pode realmente fazer muitas coisas no caminho de OO situadas em C desta forma, mesmo que pareça que algumas vezes, os conceitos OO não surgiram totalmente formados a partir da mente de #include . Eles realmente constituíam muitas das melhores práticas estabelecidas da época. OO linguagens e sistemas apenas destilados e partes amplificadas do zeitgeist de programação do dia.

A resposta para a pergunta é ‘sim, você pode’.

O kit C (OOC) orientado a objects é para aqueles que querem programar de uma maneira orientada a objects, mas também mantém o bom e velho C. OOC implementa classs, inheritance única e múltipla, exception handling.

Características

• Utiliza apenas macros e funções C, sem necessidade de extensões de idioma! (ANSI-C)

• Código-fonte de fácil leitura para o seu aplicativo. Foi tomado cuidado para tornar as coisas o mais simples possível.

• Herança única de classs

• Múltipla inheritance por interfaces e mixins (desde a versão 1.3)

• Implementando exceções (em puro C!)

• Funções virtuais para classs

• Ferramenta externa para fácil implementação de classs

Para mais detalhes, visite http://ooc-coding.sourceforge.net/ .

Parece que as pessoas estão tentando imitar o estilo C ++ usando C. Meu exemplo é que fazer programação orientada a objects C está realmente fazendo programação orientada a estruturas. No entanto, você pode conseguir coisas como binding tardia, encapsulamento e inheritance. Para inheritance, você define explicitamente um ponteiro para as estruturas base em sua subestrutura e isso é obviamente uma forma de inheritance múltipla. Você também precisará determinar se o seu

 //private_class.h struct private_class; extern struct private_class * new_private_class(); extern int ret_a_value(struct private_class *, int a, int b); extern void delete_private_class(struct private_class *); void (*late_bind_function)(struct private_class *p); //private_class.c struct inherited_class_1; struct inherited_class_2; struct private_class { int a; int b; struct inherited_class_1 *p1; struct inherited_class_2 *p2; }; struct inherited_class_1 * new_inherited_class_1(); struct inherited_class_2 * new_inherited_class_2(); struct private_class * new_private_class() { struct private_class *p; p = (struct private_class*) malloc(sizeof(struct private_class)); p->a = 0; p->b = 0; p->p1 = new_inherited_class_1(); p->p2 = new_inherited_class_2(); return p; } int ret_a_value(struct private_class *p, int a, int b) { return p->a + p->b + a + b; } void delete_private_class(struct private_class *p) { //release any resources //call delete methods for inherited classs free(p); } //main.c struct private_class *p; p = new_private_class(); late_bind_function = &implementation_function; delete_private_class(p); 

compile com c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj .

Portanto, o conselho é manter um estilo C puro e não tentar forçar um estilo C ++. Isso também se presta a uma maneira muito limpa de construir uma API.

Veja http://slkpg.byethost7.com/instance.html para mais uma reviravolta na OOP em C. Ela enfatiza os dados da instância para reinput usando apenas o nativo C. A inheritance múltipla é feita manualmente usando os wrappers de function. A segurança de tipo é mantida. Aqui está uma pequena amostra:

 typedef struct _peeker { log_t *log; symbols_t *sym; scanner_t scan; // inherited instance peek_t pk; int trace; void (*push) ( SELF *d, symbol_t *symbol ); short (*peek) ( SELF *d, int level ); short (*get) ( SELF *d ); int (*get_line_number) ( SELF *d ); } peeker_t, SlkToken; #define push(self,a) (*self).push(self, a) #define peek(self,a) (*self).peek(self, a) #define get(self) (*self).get(self) #define get_line_number(self) (*self).get_line_number(self) INSTANCE_METHOD int (get_line_number) ( peeker_t *d ) { return d->scan.line_number; } PUBLIC void InitializePeeker ( peeker_t *peeker, int trace, symbols_t *symbols, log_t *log, list_t *list ) { InitializeScanner ( &peeker->scan, trace, symbols, log, list ); peeker->log = log; peeker->sym = symbols; peeker->pk.current = peeker->pk.buffer; peeker->pk.count = 0; peeker->trace = trace; peeker->get_line_number = get_line_number; peeker->push = push; peeker->get = get; peeker->peek = peek; } 

Estou um pouco atrasado para a festa, mas quero compartilhar minha experiência sobre o tema: Eu trabalho com resources incorporados atualmente, e o único (confiável) compilador que eu tenho é C, então eu quero aplicar orientado a objects abordagem em meus projetos incorporados escritos em C.

A maioria das soluções que vi até agora usam fortemente typecasts, então perdemos a segurança do tipo: o compilador não ajudará se você cometer um erro. Isso é completamente inaceitável.

Requisitos que tenho:

  • Evite os typecasts o máximo possível, para que não percamos a segurança do tipo;
  • Polimorfismo: devemos ser capazes de usar methods virtuais, e o usuário da class não deve estar ciente se algum método particular é virtual ou não;
  • Herança Múltipla: Eu não a utilizo frequentemente, mas às vezes eu realmente quero que alguma class implemente múltiplas interfaces (ou estenda múltiplas superclasss).

I’ve explained my approach in detail in this article: Object-oriented programming in C ; plus, there is an utility for autogeneration of boilerplate code for base and derived classs.

I built a little library where I tried that and to me it works real nicely. So I thought I share the experience.

https://github.com/thomasfuhringer/oxygen

Single inheritance can be implemented quite easily using a struct and extending it for every other child class. A simple cast to the parent structure makes it possible to use parent methods on all the descendants. As long as you know that a variable points to a struct holding this kind of an object you can always cast to the root class and do introspection.

As has been mentioned, virtual methods are somewhat trickier. But they are doable. To keep things simple I just use an array of functions in the class description structure which every child class copies and repopulates individual slots where required.

Multiple inheritance would be rather complicated to implement and comes with a significant performance impact. So I leave it. I do consider it desirable and useful in quite a few cases to cleanly model real life circumstances, but in probably 90% of cases single inheritance covers the needs. And single inheritance is simple and costs nothing.

Also I do not care about type safety. I think you should not depend on the compiler to prevent you from programming mistakes. And it shields you only from a rather small part of errors anyway.

Typically, in an object oriented environment you also want to implement reference counting to automate memory management to the extent possible. So I also put a reference count into the “Object” root class and some functionality to encapsulate allocation and deallocation of heap memory.

It is all very simple and lean and gives me the essentials of OO without forcing me to deal with the monster that is C++. And I retain the flexibility of staying in C land, which among other things makes it easier to integrate third party libraries.

I propose to use Objective-C, which is a superset of C.

While Objective-C is 30 years old, it allows to write elegant code.

http://en.wikipedia.org/wiki/Objective-C

Yes, but I have never seen anyone attempt to implement any sort of polymorphism with C.