Can you write object-oriented code in C? [closed]

2018-12-31 02:16发布

Can you write object-oriented code in C? Especially with regard to polymorphism.


See also Stack Overflow question Object-orientation in C.

标签: c oop object
30条回答
余生请多指教
2楼-- · 2018-12-31 02:32

Trivial example with an Animal and Dog: You mirror C++'s vtable mechanism (largely anyway). You also separate allocation and instantiation (Animal_Alloc, Animal_New) so we don't call malloc() multiple times. We must also explicitly pass the this pointer around.

If you were to do non-virtual functions, that's trival. You just don't add them to the vtable and static functions don't require a this pointer. Multiple inheritance generally requires multiple vtables to resolve ambiguities.

Also, you should be able to use setjmp/longjmp to do exception handling.

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. This is tested on a C++ compiler, but it should be easy to make it work on a C compiler.

查看更多
几人难应
3楼-- · 2018-12-31 02:32

There are several techniques that can be used. The most important one is more how to split the project. We use an interface in our project that is declared in a .h file and the implementation of the object in a .c file. The important part is that all modules that include the .h file see only an object as a void *, and the .c file is the only module who knows the internals of the structure.

Something like this for a class we name FOO as an example:

In the .h file

#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

The C implementation file will be something like that.

#include <stdlib.h>
...
#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;
}

So I give the pointer explicitly to an object to every function of that module. A C++ compiler does it implicitly, and in C we write it explicitly out.

I really use this in my programs, to make sure that my program does not compile in C++, and it has the fine property of being in another color in my syntax highlighting editor.

The fields of the FOO_struct can be modified in one module and another module doesn't even need to be recompiled to be still usable.

With that style I already handle a big part of the advantages of OOP (data encapsulation). By using function pointers, it's even easy to implement something like inheritance, but honestly, it's really only rarely useful.

查看更多
忆尘夕之涩
4楼-- · 2018-12-31 02:32

Yes, you can. People were writing object-oriented C before C++ or Objective-C came on the scene. Both C++ and Objective-C were, in parts, attempts to take some of the OO concepts used in C and formalize them as part of the language.

Here's a really simple program that shows how you can make something that looks-like/is a method call (there are better ways to do this. This is just proof the language supports the concepts):

#include<stdio.h>

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;
}
查看更多
骚的不知所云
5楼-- · 2018-12-31 02:32

I'm a bit late to the party, but I want to share my experience on the topic: I work with embedded stuff these days, and the only (reliable) compiler I have is C, so that I want to apply object-oriented approach in my embedded projects written in C.

Most of the solutions I've seen so far use typecasts heavily, so we lose type safety: compiler won't help you if you make a mistake. This is completely unacceptable.

Requirements that I have:

  • Avoid typecasts as much as possible, so we don't lose type safety;
  • Polymorphism: we should be able to use virtual methods, and user of the class should not be aware whether some particular method is virtual or not;
  • Multiple inheritance: I don't use it often, but sometimes I really want some class to implement multiple interfaces (or to extend multiple superclasses).

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 classes.

查看更多
泪湿衣
6楼-- · 2018-12-31 02:34

You can fake it using function pointers, and in fact, I think it is theoretically possible to compile C++ programs into C.

However, it rarely makes sense to force a paradigm on a language rather than to pick a language that uses a paradigm.

查看更多
公子世无双
7楼-- · 2018-12-31 02:34

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.

查看更多
登录 后发表回答