Fabulering om OOP i C

Sent på kvelden. Satt og leste i en it-innføringsbok om fordelene ved lav kobling og å skjule interne implementasjoner da jeg tenkte på hvordan jeg kunne gjøre dette i C. Dette er ting/betraktninger jeg bare følte for å skrive ned til senere.

Hadde vært fint med «objekter» som returnerte sine (helst private) verdier for lavere kobling som i Java. I steden for
if person.alder > 5 do ...
vil jeg ha
if person.getAlder() > 5 do ...
slik at man fint kunne byttet en implementasjon av alder fra f.eks. å være statisk (person.alder=5) til dynamisk ( return now()-bDay ) uten å gjøre endringer ellers i koden.

En enkel mulighet ville være å konsekvent bruke aksessormetoder som getAlder(person) for å referere til de interne «objekt»-attributtene våre.(Nevermind at man ved å bytte direkte aksess gjennom pekere med en hel masse funksjonskall mister masse av den ytelsen C er kjent for)

typedef struct _person {
    int _fDag;
    char *_navn;
} Person;

int getAlder(Person *p) { return (now()-p->fDag)/365); }

Ytelsesmessig pyton i forhold til å bare gripe direkte til attributtet (en indirekte ref vs to funksjonskall m.m), men mye lettere gjenbrukbart. Det er allikevel ingenting som skulle si at funksjonen getAlder har noe med Person-objektene å gjøre, og man må da kanskje begynne å innføre noen prefiksgreier à la alle metodene til datatypen Person har prefikset Person_. Men så kommer problemet med duplisering av kode når det lignende datatyper. Kanskje man har «klassene» Elev og Lærer som deler de samme attributtene som Person, men i tillegg har flere. For at de skal kunne bruke en Person_getAlder() metode må den da deklareres som int Person_getAlder( void *p) og da kommer problemene på løpende bånd. Da har kompilatoren ingen anelse om hva p->alder skulel bety, for den har ingen anelse om hvilken datatype p er. Om vi nå skulle caste p til type Person via ((Person *) p)->alder må vi være helt sikre på at både Elev og Lærer har de samme variablene i eksakt samme rekkefølge, ellers blir det bananas og mulig SEGFAULT.

(Jo mer jeg skriver nå, innser jeg at jeg er glad for at noen har gjort dette skittenarbeidet for oss ved å lage oo-språk.) En løsning kunne vært å ha makroer som «arvet» innholdet fra andre klasser for å lage forskjellige «objekter». Tenker noe à la


/* Objekt Person */
#define _PERSON \
int fDay; \
char* navn;
typedef struct _person { PERSON } Person;

/*Objekt Elev*/
#define _ELEV \
PERSON /* Inkluder alt som også er i Person */
char* klasse;
typedef struct _elev { ELEV} Elev;

Når jeg tenker meg om kan jeg sikkert også få til ting som elev.getAlder() (nesten) ved den litt pythoneske kallemåten elev.getAlder(elev) om man bruker funksjonspekere som man lagrer i objektene/structene. Dette vil da med en gang også medføre at man må innføre konstruktører som initierer disse funksjonspekerne. Tror jeg i hvert fall … For kan kompilatoren vite det ved kompileringstid? Må teste senere. Usikker

Vi vil da få noe som

/* Objekt Person */
int Person_getAlder(Person p){ return ... }
#define _PERSON \
    int fDay; \
    char* navn; 
    int (*getAlder)(Person p) ; 
typedef struct _person { PERSON } Person;

Som tidligere skrevet er jeg litt usikker på om jeg også ville trengt en konstruktør for å initiere funksjonspekerne … Den ville i så fall blitt kalt når man ønsket å opprettet objektet og sett slik ut

Person *Person_init(fDay, navn)
{
    /* opprett objekt / hent minne */
    Person *p = malloc...
    /* sett variabler */
    ....
    /* sett metoder */
    p->getAlder = Person_getAlder;
}

Det blir stadig mer og mer kode her, men nå innså jeg at det jeg lurte på har to svar:

  1. For objekter som blir opprettet i det koden kjører trenger man ingen konstruktør
  2. For objekter (som over) som blir dynamisk opprettet ved behov må man selvfølgelig initiere det uinitialiserte minneområdet med noe. Duh.

Generelle konstruktører mulig
Skulle gjerne hatt noen halvveis generaliserte konstruktører som kunne automatisere en del av jobben

Person * Person_init(int fDay, char *navn)
{
    Person *p = CREATE(Person); /* Generell makro som oppretter et nytt objekt av type Person */
    INIT_VAR(Person,p,fDay,navn); /* Generell makro som aksepterer et variabelt antall argumenter */
    INIT_FUNCS(Person,p,getNavn,getAlder,nullStill); /* Makro med VAR_ARG som setter funksjonspekere */
}

Et «kall» til INIT_FUNCS(MinKlasse,objektPeker,funk1,funk2) ville blant annet føre til følgende kode (pre-prosessoren hadde ekspandert makroene og substituert strenger):
((MinKlasse) objektPeker)->funk1 = MinKlasse_funk1;

For hvordan man faktisk jobber med å ekspandere et variabelt antall argumenter, se følgende.

INIT_FUNCS hadde gjort seg bruk av streng substituering som Class##_##FUNC_NAME for å få navn som MinKlasse_funk1. Jeg skjønner bare ikke hvordan man bruker __VA_ARGS__ iterativt, ettersom den bare utvides til å være alle argumentene. Kanskje det er her det stopper? For det går vel ikke an å gjøre det som en funksjon, eller … ?

Ett svar til Fabulering om OOP i C

  1. Carl-Erik sier:

    Fant noen fine artikler om emnet:
    «Single Inheritance Classes in C» (Dr. Dobbs)
    «Portable Inheritance and Polymorphism in C» (Embedded Systems)

Legg igjen en kommentar

Fyll inn i feltene under, eller klikk på et ikon for å logge inn:

WordPress.com-logo

Du kommenterer med bruk av din WordPress.com konto. Logg ut / Endre )

Twitter picture

Du kommenterer med bruk av din Twitter konto. Logg ut / Endre )

Facebookbilde

Du kommenterer med bruk av din Facebook konto. Logg ut / Endre )

Google+ photo

Du kommenterer med bruk av din Google+ konto. Logg ut / Endre )

Kobler til %s

%d bloggers like this: