Är det värt att komplettera lite grundläggande OOP i C, istället för att lära sig C++?

Permalänk

Är det värt att komplettera lite grundläggande OOP i C, istället för att lära sig C++?

Hej!

Jag har skapat en klass i rent C och satt några värden till en variabel i klassen.
Jag har aldrig gjort detta förut, men nu börjar jag förstå kraften av pekare.
Dessutom fann jag detta väldigt roligt och väldigt enkelt också. Jag har skapat en klass som heter minKlass och den har en variabel som heter minVariabel. För att sätta värden och hämta värden till minVariabel så har jag skapat två funktioner som heter getter och setter. Detta är en väldigt lik analogi med Javaprogrammering att man sätter getter och setter.

Det har varit en rätt aktivt diskussion att OOP är faktiskt inte den bästa metodiken att lösa problem. Majoriteten av fallen så duger faktiskt funktionsprogrammering, dvs vanliga funktioner. Där håller jag faktiskt med. Jag tycker funktionsprogrammering är enklare att läsa och det blir dessutom mindre kod.

Men trots att funktionsprogrammering må vara den vanligaste metodiken för att lösa dagliga problem, så finns det ändå behov utav OOP vid tillfällen där man jobbar med objekt. Då tänkte jag ställa er frågan:

Frågeställning:
Är det värt att komplettera lite grundläggande OOP i C, istället för att lära sig C++?
Jag blir riktigt nöjd om jag kan bara göra en liten klass och ha den möjligheten att kunna ärva en annan klass. Mer än så här behöver jag inte för att kunna lösa OOP problem. Nu finns det säkert ännu mera man kan göra inom OOP t.ex. interface. Men sådant har jag faktiskt klarat mig utan.

Här nedan visar jag två exempel utan minnesallokering. Dvs du behöver inte använda funktionen free som i C ochC++ efter avslutat objekt.

#include <stdio.h> typedef struct minKlass minKlass; struct minKlass{ /* Skapa en variabel och getter-setter */ int minVariabel; void (*setMinVariable)(minKlass*, int); int (*getMinVariable)(minKlass*); }; minKlass* minKonstruktor(minKlass* this){ /* Skapa getter-funktion */ int getMinVariableFunktion(minKlass* this){ return this->minVariabel; } /* Skapa setter-funktion */ void setMinVariableFunktion(minKlass* this, int variabel){ this->minVariabel = variabel; } /* Applicera dessa till pekarfunktionen i klassen */ this->setMinVariable = setMinVariableFunktion; this->getMinVariable = getMinVariableFunktion; /* Retunera adressen av våran nya klass */ return this; } int main(){ /* Skapa två klasser */ minKlass klass1; minKlass klass2; /* Passera klassen igenom konstruktören */ minKlass* pKlass1 = minKonstruktor(&klass1); minKlass* pKlass2 = minKonstruktor(&klass2); /* Sätt ett värde till klassen */ pKlass1->setMinVariable(pKlass1, 104); pKlass2->setMinVariable(pKlass2, 23); /* Ta ett värde från klassen */ int minVariabel1 = pKlass1->minVariabel; int minVariabel2 = pKlass2->minVariabel; /* Skriv ut värdet */ printf("minVariabel1 = %i, minVariabel2 = %i\n", minVariabel1, minVariabel2); return 0; }

Om jag vill ha lite arv där jag skapar en huvudKlass som är inkluderat i minKlass.

typedef struct minKlass minKlass; struct minKlass{ /* Skapa en variabel och getter-setter */ int minVariabel; void (*setMinVariabel)(minKlass*, int); int (*getMinVariabel)(minKlass*); /* Ärv huvudklassen */ huvudKlass* huvud; };

Analogin mellan klasserna är det samma. Skillnaden är att jag MÅSTE skapa strukturen innan jag anropar min konstruktör.
Om jag inte vill göra detta, så måste jag allokera minnet igenom att använda mig utav malloc. Men notera, använder man malloc, så måste man även frigöra minnet sedan via free. Tänk på det!

/* Skapa två klasser */ minKlass minKlass1; minKlass minKlass2; huvudKlass huvudKlass1; huvudKlass huvudKlass2; /* Viktigt att sätta addresserna från huvudklassen till min klass */ minKlass1.huvud = &huvudKlass1; minKlass2.huvud = &huvudKlass2;

#include <stdio.h> typedef struct huvudKlass huvudKlass; struct huvudKlass{ int huvudVariabel; void (*setHuvudVariabel)(huvudKlass*, int); int (*getHuvudVariabel)(huvudKlass*); }; huvudKlass* huvudKonstruktor(huvudKlass* this){ /* Skapa getter-funktion */ int getHuvudVariabelFunktion(huvudKlass* this){ return this->huvudVariabel; } /* Skapa setter-funktion */ void setHuvudVariabelFunktion(huvudKlass* this, int variabel){ this->huvudVariabel = variabel; } /* Applicera dessa till pekarfunktionen i klassen */ this->setHuvudVariabel = setHuvudVariabelFunktion; this->getHuvudVariabel = getHuvudVariabelFunktion; /* Retunera adressen av våran nya klass */ return this; } typedef struct minKlass minKlass; struct minKlass{ /* Skapa en variabel och getter-setter */ int minVariabel; void (*setMinVariabel)(minKlass*, int); int (*getMinVariabel)(minKlass*); /* Ärv huvudklassen */ huvudKlass* huvud; }; minKlass* minKonstruktor(minKlass* this){ /* Skapa getter-funktion */ int getMinVariabelFunktion(minKlass* this){ return this->minVariabel; } /* Skapa setter-funktion */ void setMinVariabelFunktion(minKlass* this, int variabel){ this->minVariabel = variabel; } /* Applicera dessa till pekarfunktionen i klassen */ this->setMinVariabel = setMinVariabelFunktion; this->getMinVariabel = getMinVariabelFunktion; /* Anropa konstruktören för huvudklassen */ this->huvud = huvudKonstruktor(this->huvud); /* Retunera adressen av våran nya klass */ return this; } int main(){ /* Skapa två klasser */ minKlass minKlass1; minKlass minKlass2; huvudKlass huvudKlass1; huvudKlass huvudKlass2; /* Viktigt att sätta addresserna från huvudklassen till min klass */ minKlass1.huvud = &huvudKlass1; minKlass2.huvud = &huvudKlass2; /* Passera klassen igenom konstruktören */ minKlass* pMinKlass1 = minKonstruktor(&minKlass1); minKlass* pMinKlass2 = minKonstruktor(&minKlass2); /* Sätt ett värde till minKlassen */ pMinKlass1->setMinVariabel(pMinKlass1, 104); pMinKlass2->setMinVariabel(pMinKlass2, 23); /* Sätt ett värde till huvudKlassen */ pMinKlass1->huvud->setHuvudVariabel(pMinKlass1->huvud, 200); pMinKlass2->huvud->setHuvudVariabel(pMinKlass2->huvud, 500); /* Ta ett värde från klassen */ int minVariabel1 = pMinKlass1->minVariabel; int minVariabel2 = pMinKlass2->minVariabel; int huvudVariabel1 = pMinKlass1->huvud->getHuvudVariabel(pMinKlass1->huvud); int huvudVariabel2 = pMinKlass2->huvud->getHuvudVariabel(pMinKlass2->huvud); /* Skriv ut värdet */ printf("minVariabel1 = %i, minVariabel2 = %i, huvudVariabel1 = %i, huvudVariabel2 = %i\n", minVariabel1, minVariabel2, huvudVariabel1, huvudVariabel2); return 0; }

Permalänk
Medlem

Motsvarande C++-kod skulle kunna se ut så här:

#include <iostream> class HuvudKlass { public: HuvudKlass(int val) : huvudVariabel(val) {} void setHuvudVariabel(int val) { huvudVariabel = val; } int getHuvudVariabel() { return huvudVariabel; } private: int huvudVariabel; }; class MinKlass : public HuvudKlass { public: MinKlass(int huvud_val, int min_val) : HuvudKlass(huvud_val), minVariabel(min_val) {} void setMinVariabel(int val) { minVariabel = val; } int getMinVariabel() { return minVariabel; } private: int minVariabel; }; int main() { MinKlass minKlass1(104, 200); MinKlass minKlass2(23, 500); auto minVariabel1 = minKlass1.getMinVariabel(); auto minVariabel2 = minKlass2.getMinVariabel(); auto huvudVariabel1 = minKlass1.getHuvudVariabel(); auto huvudVariabel2 = minKlass2.getHuvudVariabel(); std::cout << "minVariabel1 = " << minVariabel1 << ", minVariabel2 = " << minVariabel2 << ", huvudVariabel1 = " << huvudVariabel1 << ", huvudVariabel2 = " << huvudVariabel2 << std::endl; }

Förutom att koden är mycket kortare så har den många fördelar över din C-kod. Din kod har många problem, t.ex.:

  • Det krävs flera steg för att konstruera ett objekt, och objekten innehåller ändå oinitialiserade variabler efter konstruktorn.

  • Att användaren själv måste klistra ihop en klass med klassen den ärver ifrån är en stor felkälla.

  • Du har ingen polymorfism, d.v.s. du kan inte skriva en funktion som tar emot en instans av vilken klass som helst som ärver från huvudKlass och ändå beter sig som klassen den egentligen är. Utan detta så missar du en av de största fördelarna med OOP.

  • Du har ingen dataenkapsulering, användarna kan komma åt variablerna i objekten direkt och kringå get/set-metoderna. Hela poängen med såna metoder är att kontrollera användarens åtkomst.

Sen är get/set-metoder ofta ett tecken på dålig OOP-design. Ibland är de nödvändiga, men ofta används de bara för att tillåta direkt åtkomst till variabler vilket gör att man lika gärna kan göra variablerna public istället. Det är bättre att försöka designa sina klasser så att användaren behöver veta så lite som möjligt om vilken data de innehåller.

Och även när man behöver ha get/set-metoder så föredrar jag att undvika get/set-namn och istället säga vad de gör, t.ex. point.moveTo(100, 200) istället för point.setPosition(100, 200). Men det är bara min åsikt.

Permalänk
Hedersmedlem
Skrivet av perost:

Motsvarande C++-kod skulle kunna se ut så här:

#include <iostream> class HuvudKlass { public: HuvudKlass(int val) : huvudVariabel(val) {} void setHuvudVariabel(int val) { huvudVariabel = val; } int getHuvudVariabel() { return huvudVariabel; } private: int huvudVariabel; }; class MinKlass : public HuvudKlass { public: MinKlass(int huvud_val, int min_val) : HuvudKlass(huvud_val), minVariabel(min_val) {} void setMinVariabel(int val) { minVariabel = val; } int getMinVariabel() { return minVariabel; } private: int minVariabel; }; int main() { MinKlass minKlass1(104, 200); MinKlass minKlass2(23, 500); auto minVariabel1 = minKlass1.getMinVariabel(); auto minVariabel2 = minKlass2.getMinVariabel(); auto huvudVariabel1 = minKlass1.getHuvudVariabel(); auto huvudVariabel2 = minKlass2.getHuvudVariabel(); std::cout << "minVariabel1 = " << minVariabel1 << ", minVariabel2 = " << minVariabel2 << ", huvudVariabel1 = " << huvudVariabel1 << ", huvudVariabel2 = " << huvudVariabel2 << std::endl; }

Förutom att koden är mycket kortare så har den många fördelar över din C-kod. Din kod har många problem, t.ex.:

  • Det krävs flera steg för att konstruera ett objekt, och objekten innehåller ändå oinitialiserade variabler efter konstruktorn.

  • Att användaren själv måste klistra ihop en klass med klassen den ärver ifrån är en stor felkälla.

  • Du har ingen polymorfism, d.v.s. du kan inte skriva en funktion som tar emot en instans av vilken klass som helst som ärver från huvudKlass och ändå beter sig som klassen den egentligen är. Utan detta så missar du en av de största fördelarna med OOP.

  • Du har ingen dataenkapsulering, användarna kan komma åt variablerna i objekten direkt och kringå get/set-metoderna. Hela poängen med såna metoder är att kontrollera användarens åtkomst.

Sen är get/set-metoder ofta ett tecken på dålig OOP-design. Ibland är de nödvändiga, men ofta används de bara för att tillåta direkt åtkomst till variabler vilket gör att man lika gärna kan göra variablerna public istället. Det är bättre att försöka designa sina klasser så att användaren behöver veta så lite som möjligt om vilken data de innehåller.

Och även när man behöver ha get/set-metoder så föredrar jag att undvika get/set-namn och istället säga vad de gör, t.ex. point.moveTo(100, 200) istället för point.setPosition(100, 200). Men det är bara min åsikt.

Eh, polymorfism finns ju visst? Han har funktionspekare i varje objekt. Inte den mest effektiva implementationen av polymorfism men den finns där.

Permalänk
Medlem
Skrivet av pv2b:

Eh, polymorfism finns ju visst? Han har funktionspekare i varje objekt. Inte den mest effektiva implementationen av polymorfism men den finns där.

Ah, missade det bland allt annat. Men jo, det går ju att göra det mesta i C om man vill, det blir bara mycket krångligare och mer felbenäget. OOP i C++ är inte perfekt heller, men man måste vara en riktig inbiten C-fanatiker för att koda OOP i C

Permalänk
Skrivet av perost:

Det krävs flera steg för att konstruera ett objekt, och objekten innehåller ändå oinitialiserade variabler efter konstruktorn.

Detta har med att jag allokerar inte dynamiskt minne. Skulle jag göra det, så blir stegen allt färre. En fördel med att icke allokera dynamiskt minne är väldigt viktigt inom inbyggda system som är säkerhetskritiska där man måste ha koll på hur många byte man använder.

Citat:

Att användaren själv måste klistra ihop en klass med klassen den ärver ifrån är en stor felkälla.[/li]

Ja. Detta måste man. Men man får funktionen att den ärver, än fast det är manuellt gjort.

Citat:

Du har ingen polymorfism, d.v.s. du kan inte skriva en funktion som tar emot en instans av vilken klass som helst som ärver från huvudKlass och ändå beter sig som klassen den egentligen är. Utan detta så missar du en av de största fördelarna med OOP.

Har jag inte?

Citat:

Du har ingen dataenkapsulering, användarna kan komma åt variablerna i objekten direkt och kringå get/set-metoderna. Hela poängen med såna metoder är att kontrollera användarens åtkomst.

Hur kan man göra detta i C?

Permalänk
Skrivet av perost:

Ah, missade det bland allt annat. Men jo, det går ju att göra det mesta i C om man vill, det blir bara mycket krångligare och mer felbenäget. OOP i C++ är inte perfekt heller, men man måste vara en riktig inbiten C-fanatiker för att koda OOP i C

Sjävklart är C krångligare än C++ om man använder det på fel sätt Alla språk har sina syften.
Jag började faktiskt med C++98 men jag var för dum för att kunna förstå det. Dessutom lockade Java Swing betydligt mycket mer än grafiska terminaler. Så valet blev Java. Om jag hade en önskning så önskade jag att Java Swing blev standard när det kommer till webbutveckling. Java Applets är det finaste jag vet.

Tänk om man gick in på Microsofts hemsida och sedan PANG....ett stort kaffetecken rakt upp i ansiktet...LOADING....Sedan kommer Windows 95 internet explorer upp som man får navigera sig runt på hemsidan. Juvliga tider.

Men trots att man kan ett högnivåspråk, så finns ändå behov utav språk på lägre nivåer. Jag lärde mig C. Under mina arbetsår så har jag funnit C mer attraktivt än C++. Inte för att C skulle vara bättre språk än C++. Utan för att varje bibliotek man laddar ned, så finns det alltid ett C API tillgängligt.

Jag tolkar detta som att det finns en möjlighet för företag att investera sin tid i C jämfört med C++, då C har inte ändrats något sedan C99, dvs 22 år sedan. Jämfört med C++ som uppdateras allt oftare och underhålla och uppdatera kod efter senaste standard är väldigt dyrt.

Det är därför jag undrar om man ska investera lite OOP-kunskaper inom C. Bara så att man får funktionen.

Permalänk
Skrivet av pv2b:

Eh, polymorfism finns ju visst? Han har funktionspekare i varje objekt. Inte den mest effektiva implementationen av polymorfism men den finns där.

Om det finns annat sätt att skriva om min C kod, så visa mig gärna.
Helst så enkelt som möjligt.

Permalänk
Hedersmedlem
Skrivet av heretic16:

Om det finns annat sätt att skriva om min C kod, så visa mig gärna.
Helst så enkelt som möjligt.

Enkelt och effektivt är tyvärr motsatserna till varandra i det här fallet.

Genom att göra enkelt som du har gjort (lagra funktionspekare direkt i structen) så har du ju fördelen med att syntaxen är rätt enkel vid själva funktionsanropen. Men du har några nackdelar.

Varje instans av objektet behöver sina egna exemplar av funktionspekare. Om du har 1000 instanser av din struct minklass så kommer du också ha 1000 funktionspekare till getMinVariable och setMinVariable. Det var det jag menade med "inte den effektivaste implementationen".

Detta kan du göra genom att istället ha en enda pekare till ett slags "klassobjekt". Typ som nedan (inte testat, endast en princip:)

struct minKlass_instans { struct *minKlass_klass klass; /* Skapa en variabel och getter-setter */ int minVariabel; }; struct minKlass_klass { void (*setMinVariable)(minKlass*, int); int (*getMinVariable)(minKlass*); };

Alla funktionsanrop blir då krångligare i koden. Det blir typ:

struct minKlass_instans *instans = /* .... */; instans->klass->setMinVariable(instans, 42);

Det är något liknande som C++-implementationer gör med sina "vtables" tror jag, men jag kan för lite om de interna mekanismerna i C++ för att säga säkert.

(En smidig sidoeffekt av detta är att du kan använda själva klasspekaren för att dynamiskt vid körtid kunna kolla vilken klass ett visst objekt tillhör! Något som ofta kan komma vara användbart när man ska köra en typkontroll när man typecastat sin pekare...)

Alla funktionsanrop behöver ju inte heller nödvändigtvis kunna stödja polymorfism heller? Därför det i C++ finns ett keyword "virtual" som gör att alla funktionsanrop inte behöver gå omvägen genom att kolla en slags funktionstabell... inget virtual-keyword så är det bara en "vanlig" funktion med statisk addressering.

Du kommer dessutom ha problem så fort du vill lägga till metoder med hjälp av arv. Du behöver ju i princip då kunna lägga till flera fält i din struct. Du kan ju lösa det genom att casta fram och tillbaka, och se till att du alltid har samma layout i din struct för din basklass och att eventuella subklasser har samma layout i början, för att sedan lägga till nya fält på slutet. Lycka till med att hålla detta "in sync" när du editerar din basklass och sedan måste ändra alla klasser du ärver från...

Men frågan är ju faktiskt, BEHÖVER du polymorfism? Om inte så har du gjort ditt liv mycket enklare. Då kan du istället göra typ:

struct minKlass { int minVariabel; }; void minKlass_setMinVariable(minKlass *instans, int nyttVarde) { instans->minVariabel = nyttVarde; } int minKlass_getMinVariable(minKlass *instans) { return instans->minVariabel; } /* som sedan kan anropas som minKlass_setMinVariable(instans, 42); etc */

Du slipper då bära runt en massa funktionspekare kring varje objekt, och har en nättare anropssyntax. Och behöver du verkligen polymorfism på vissa specifika funktioner så är det kanske vettigare att skriva olika cases i dina funktioner istället för att försöka återuppfinna ett sämre hjul. Eller kanske någon funktionspekare någonstans.

Mitt förslag: Ska du nödvändigtvis koda OO, använd då ett programmeringsspråk avsett för det. Men OO är inte på något sätt en slags magisk silverkula som löser en massa problem inom programmering, faktum är att oftast så behövs det inte alls, och koden blir både enklare att förstå, skriva, och använda, än om man lusar ner koden med en massa arv och liknande OO-bös.

Permalänk
Datavetare
Skrivet av heretic16:

Sjävklart är C krångligare än C++ om man använder det på fel sätt Alla språk har sina syften.
Jag började faktiskt med C++98 men jag var för dum för att kunna förstå det. Dessutom lockade Java Swing betydligt mycket mer än grafiska terminaler. Så valet blev Java. Om jag hade en önskning så önskade jag att Java Swing blev standard när det kommer till webbutveckling. Java Applets är det finaste jag vet.

Tänk om man gick in på Microsofts hemsida och sedan PANG....ett stort kaffetecken rakt upp i ansiktet...LOADING....Sedan kommer Windows 95 internet explorer upp som man får navigera sig runt på hemsidan. Juvliga tider.

Men trots att man kan ett högnivåspråk, så finns ändå behov utav språk på lägre nivåer. Jag lärde mig C. Under mina arbetsår så har jag funnit C mer attraktivt än C++. Inte för att C skulle vara bättre språk än C++. Utan för att varje bibliotek man laddar ned, så finns det alltid ett C API tillgängligt.

Jag tolkar detta som att det finns en möjlighet för företag att investera sin tid i C jämfört med C++, då C har inte ändrats något sedan C99, dvs 22 år sedan. Jämfört med C++ som uppdateras allt oftare och underhålla och uppdatera kod efter senaste standard är väldigt dyrt.

Det är därför jag undrar om man ska investera lite OOP-kunskaper inom C. Bara så att man får funktionen.

OOP har absolut sina användningsområden, men de är relativt få och det har nog många dyrt fått erfara efter OOP hypen som följde med hypen runt Java och C# runt millennieskiftet. OOP har även sina poänger om man är bunden till C, det används t.ex. i flertalet I/O-system i diverse OS-kärnor.

Men om man inte skriver OS-kärnor (fast även här sker ändringar då man ser de uppenbara tillkortakommanden i C, t.ex. är OS-kärnan i Fuchsia skriven i C++ och finns flera som tittar på att använda Rust) och inte jobbar på en kodbas som redan består av en massiv mängd C-kod finns väldigt få rationella anledningar att jobba i C.

Även om man siktar på de minsta inbyggda systemen har C++ och och än mer Rust flertalet fördelar. Bl.a. blir minneshanteringen betydligt enklare, framförallt om man jobbar med realtidssystem där allt minne måste allokeras "up-front".

"Modern" C++ har flera finesser som rätt använda i praktiken leder till program som är lika snabb eller snabbare än motsvarande program skrivet i idiomatisk C. Rust har den gigantiska fördelen att rätt använt kan det ge kompileringsfel vid inkorrekt användning av minne!

Att så många bibliotek presenterar ett C API har en tekniskt orsak: ISO C definierar, till skillnad från ISO C++, en standard för binärkompatibilitet och Windows, Linux och MacOS har alla binärstandarder som bygger på C. C++ har alltid kunnat använda C-bibliotek rakt av, allt fler har insett värdet av att lägga in konsumtion av C-bibliotek som en standard-feature i språket och vi ser detta i att t.ex. Go och Rust definierar interoperabilitet med C som en del av språkdefinitionen. I fallet Rust är det precis som i C++ helt utan overhead att anropa C (Go har viss prestandamässig overhead men enkelt att använda, i språk som Java och C# kan det ofta vara rätt rejäl overhead att använda C både prestandamässigt och boilerplatemässigt).

C99 är förövrigt inte senaste standard för C, det är C11 och man jobbar på en C2x standard. Problemet här är att Microsoft har i praktiken inte implementerat något senare än C89, Linux/MacOS har i praktiken bara C99 och vissa delar av C11. Finns flera RTOS som implementerar hela C11. För "vanlig" program gissar jag att alla inser det poänglösa i att välja C över C++, men C kommer leva kvar "för alltid" just p.g.a. att det är enda alternativet för kod i flera av våra mest använda OS-kärnor.

Det går att städa din C kod rätt ordentligt, sättet finns framförallt betydligt bättre sätt att implementera arv i C. Går även att få till enkapsulering av tillstånd, men tyvärr har det en tendens att gå via void* eller liknande. Kort och gott: det är krångligare än C++ och det kommer också bli långsammare då det finns mindre kontext för kompilatorn att jobba med, så i praktiken helt meningslöst om man inte absolut är tvingad till det p.g.a. att man t.ex. jobbar på Linux-kärnan eller liknande.

För dina egna projekt finns det ingen som tvingar dig att använda alla finesser i C++. Kolla på Arduino, det är inte C++, det är i praktiken C med tillägg specifikt för det du försöker göra här ovan. Och där kör man på 8-bitars CPUer, ofta helt utan dynamisk minnesallokering.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av pv2b:

Enkelt och effektivt är tyvärr motsatserna till varandra i det här fallet.

Genom att göra enkelt som du har gjort (lagra funktionspekare direkt i structen) så har du ju fördelen med att syntaxen är rätt enkel vid själva funktionsanropen. Men du har några nackdelar.

Varje instans av objektet behöver sina egna exemplar av funktionspekare. Om du har 1000 instanser av din struct minklass så kommer du också ha 1000 funktionspekare till getMinVariable och setMinVariable. Det var det jag menade med "inte den effektivaste implementationen".

Detta kan du göra genom att istället ha en enda pekare till ett slags "klassobjekt". Typ som nedan (inte testat, endast en princip:)

struct minKlass_instans { struct *minKlass_klass klass; /* Skapa en variabel och getter-setter */ int minVariabel; }; struct minKlass_klass { void (*setMinVariable)(minKlass*, int); int (*getMinVariable)(minKlass*); };

Alla funktionsanrop blir då krångligare i koden. Det blir typ:

struct minKlass_instans *instans = /* .... */; instans->klass->setMinVariable(instans, 42);

Det är något liknande som C++-implementationer gör med sina "vtables" tror jag, men jag kan för lite om de interna mekanismerna i C++ för att säga säkert.

(En smidig sidoeffekt av detta är att du kan använda själva klasspekaren för att dynamiskt vid körtid kunna kolla vilken klass ett visst objekt tillhör! Något som ofta kan komma vara användbart när man ska köra en typkontroll när man typecastat sin pekare...)

Alla funktionsanrop behöver ju inte heller nödvändigtvis kunna stödja polymorfism heller? Därför det i C++ finns ett keyword "virtual" som gör att alla funktionsanrop inte behöver gå omvägen genom att kolla en slags funktionstabell... inget virtual-keyword så är det bara en "vanlig" funktion med statisk addressering.

Du kommer dessutom ha problem så fort du vill lägga till metoder med hjälp av arv. Du behöver ju i princip då kunna lägga till flera fält i din struct. Du kan ju lösa det genom att casta fram och tillbaka, och se till att du alltid har samma layout i din struct för din basklass och att eventuella subklasser har samma layout i början, för att sedan lägga till nya fält på slutet. Lycka till med att hålla detta "in sync" när du editerar din basklass och sedan måste ändra alla klasser du ärver från...

Men frågan är ju faktiskt, BEHÖVER du polymorfism? Om inte så har du gjort ditt liv mycket enklare. Då kan du istället göra typ:

struct minKlass { int minVariabel; }; void minKlass_setMinVariable(minKlass *instans, int nyttVarde) { instans->minVariabel = nyttVarde; } int minKlass_getMinVariable(minKlass *instans) { return instans->minVariabel; } /* som sedan kan anropas som minKlass_setMinVariable(instans, 42); etc */

Du slipper då bära runt en massa funktionspekare kring varje objekt, och har en nättare anropssyntax. Och behöver du verkligen polymorfism på vissa specifika funktioner så är det kanske vettigare att skriva olika cases i dina funktioner istället för att försöka återuppfinna ett sämre hjul. Eller kanske någon funktionspekare någonstans.

Mitt förslag: Ska du nödvändigtvis koda OO, använd då ett programmeringsspråk avsett för det. Men OO är inte på något sätt en slags magisk silverkula som löser en massa problem inom programmering, faktum är att oftast så behövs det inte alls, och koden blir både enklare att förstå, skriva, och använda, än om man lusar ner koden med en massa arv och liknande OO-bös.

Jag förstår vad du menar. Det är bättre att ha en struktur som man använder som argument i vissa funktioner och dessa funktionerna behandlar strukturen. Funktioner som man har som statiska, kan betraktas som privata funktioner.

Jag bara utforskar möjligheten med OOP hos C.
Här är ett exempel på dynamisk allokering. Mycket mindre kod nu.
Om man bara skulle kunna göra variablerna privata så vore detta klockrent

#include <stdio.h> #include <stdlib.h> typedef struct huvudKlass huvudKlass; struct huvudKlass{ int huvudVariabel; void (*setHuvudVariabel)(huvudKlass*, int); int (*getHuvudVariabel)(huvudKlass*); }; huvudKlass* huvudKonstruktor(){ /* Allokera dynamiskt minne */ huvudKlass* this = (huvudKlass*)malloc(sizeof(huvudKlass)); /* Skapa getter-funktion */ int getHuvudVariabelFunktion(huvudKlass* this){ return this->huvudVariabel; } /* Skapa setter-funktion */ void setHuvudVariabelFunktion(huvudKlass* this, int variabel){ this->huvudVariabel = variabel; } /* Applicera dessa till pekarfunktionen i klassen */ this->setHuvudVariabel = setHuvudVariabelFunktion; this->getHuvudVariabel = getHuvudVariabelFunktion; /* Retunera adressen av våran nya klass */ return this; } typedef struct minKlass minKlass; struct minKlass{ /* Skapa en variabel och getter-setter */ int minVariabel; void (*setMinVariabel)(minKlass*, int); int (*getMinVariabel)(minKlass*); /* Ärv huvudklassen */ huvudKlass* huvud; }; minKlass* minKonstruktor(){ /* Allokera dynamiskt minne */ minKlass* this = (minKlass*)malloc(sizeof(minKlass)); /* Skapa getter-funktion */ int getMinVariabelFunktion(minKlass* this){ return this->minVariabel; } /* Skapa setter-funktion */ void setMinVariabelFunktion(minKlass* this, int variabel){ this->minVariabel = variabel; } /* Applicera dessa till pekarfunktionen i klassen */ this->setMinVariabel = setMinVariabelFunktion; this->getMinVariabel = getMinVariabelFunktion; /* Anropa konstruktören för huvudklassen */ this->huvud = huvudKonstruktor(); /* Retunera adressen av våran nya klass */ return this; } void dekonstruktor(minKlass* this){ free(this->huvud); free(this); } int main(){ /* Skapa två objekt */ minKlass* pMinKlass1 = minKonstruktor(); minKlass* pMinKlass2 = minKonstruktor(); /* Sätt ett värde till minKlassen */ pMinKlass1->setMinVariabel(pMinKlass1, 104); pMinKlass2->setMinVariabel(pMinKlass2, 23); /* Sätt ett värde till huvudKlassen */ pMinKlass1->huvud->setHuvudVariabel(pMinKlass1->huvud, 200); pMinKlass2->huvud->setHuvudVariabel(pMinKlass2->huvud, 500); /* Ta ett värde från klassen */ int minVariabel1 = pMinKlass1->minVariabel; int minVariabel2 = pMinKlass2->minVariabel; int huvudVariabel1 = pMinKlass1->huvud->getHuvudVariabel(pMinKlass1->huvud); int huvudVariabel2 = pMinKlass2->huvud->getHuvudVariabel(pMinKlass2->huvud); /* Skriv ut värdet */ printf("minVariabel1 = %i, minVariabel2 = %i, huvudVariabel1 = %i, huvudVariabel2 = %i\n", minVariabel1, minVariabel2, huvudVariabel1, huvudVariabel2); /* Förstör objekten */ dekonstruktor(pMinKlass1); dekonstruktor(pMinKlass2); return 0; }

Permalänk
Skrivet av Yoshman:

OOP har absolut sina användningsområden, men de är relativt få och det har nog många dyrt fått erfara efter OOP hypen som följde med hypen runt Java och C# runt millennieskiftet. OOP har även sina poänger om man är bunden till C, det används t.ex. i flertalet I/O-system i diverse OS-kärnor.

Men om man inte skriver OS-kärnor (fast även här sker ändringar då man ser de uppenbara tillkortakommanden i C, t.ex. är OS-kärnan i Fuchsia skriven i C++ och finns flera som tittar på att använda Rust) och inte jobbar på en kodbas som redan består av en massiv mängd C-kod finns väldigt få rationella anledningar att jobba i C.

Även om man siktar på de minsta inbyggda systemen har C++ och och än mer Rust flertalet fördelar. Bl.a. blir minneshanteringen betydligt enklare, framförallt om man jobbar med realtidssystem där allt minne måste allokeras "up-front".

"Modern" C++ har flera finesser som rätt använda i praktiken leder till program som är lika snabb eller snabbare än motsvarande program skrivet i idiomatisk C. Rust har den gigantiska fördelen att rätt använt kan det ge kompileringsfel vid inkorrekt användning av minne!

Att så många bibliotek presenterar ett C API har en tekniskt orsak: ISO C definierar, till skillnad från ISO C++, en standard för binärkompatibilitet och Windows, Linux och MacOS har alla binärstandarder som bygger på C. C++ har alltid kunnat använda C-bibliotek rakt av, allt fler har insett värdet av att lägga in konsumtion av C-bibliotek som en standard-feature i språket och vi ser detta i att t.ex. Go och Rust definierar interoperabilitet med C som en del av språkdefinitionen. I fallet Rust är det precis som i C++ helt utan overhead att anropa C (Go har viss prestandamässig overhead men enkelt att använda, i språk som Java och C# kan det ofta vara rätt rejäl overhead att använda C både prestandamässigt och boilerplatemässigt).

C99 är förövrigt inte senaste standard för C, det är C11 och man jobbar på en C2x standard. Problemet här är att Microsoft har i praktiken inte implementerat något senare än C89, Linux/MacOS har i praktiken bara C99 och vissa delar av C11. Finns flera RTOS som implementerar hela C11. För "vanlig" program gissar jag att alla inser det poänglösa i att välja C över C++, men C kommer leva kvar "för alltid" just p.g.a. att det är enda alternativet för kod i flera av våra mest använda OS-kärnor.

Det går att städa din C kod rätt ordentligt, sättet finns framförallt betydligt bättre sätt att implementera arv i C. Går även att få till enkapsulering av tillstånd, men tyvärr har det en tendens att gå via void* eller liknande. Kort och gott: det är krångligare än C++ och det kommer också bli långsammare då det finns mindre kontext för kompilatorn att jobba med, så i praktiken helt meningslöst om man inte absolut är tvingad till det p.g.a. att man t.ex. jobbar på Linux-kärnan eller liknande.

För dina egna projekt finns det ingen som tvingar dig att använda alla finesser i C++. Kolla på Arduino, det är inte C++, det är i praktiken C med tillägg specifikt för det du försöker göra här ovan. Och där kör man på 8-bitars CPUer, ofta helt utan dynamisk minnesallokering.

Jag har ingen erfarenhet av Rust så jag vet inte. Men jag tror inte Rust kommer ersätta C++ eller C. Orsaken har med ISO standarder att göra. C verkar vara det språk som är bäst att skapa en standard med, trots att C++ är ett bättre språk funktionsmässigt.

Orsaken varför jag tycker att C++ är mer avancerat än C har med just OOP. Ibland när jag läser C++ kod så förstår jag inget.
Javakod är samma sak. De flesta skriver som krattor. Då menar jag inte att dom är dålig på att skriva kod, utan dom skriver bara kod som dom själva förstår. Någon gömd klass hit och dit och helt plötsligt en ny arbetsmetodik och struktur på projektet.

C18 är senaste standarden. Men C18 och C99 skiljer sig knappt. Något extra macro eller så.

Jag tror att en viss kunskap inom OOP inom C är aldrig fel. Bara så att man får funktionen. Sedan om den saknar enkapsulering eller generics. Det kvittar för min del.

Det är därför jag söker denna kunskap. Du får gärna visa mig vad man kan göra bättre än min senaste kod ovan Bara för kul. Alltid roligt att gräva i djupet för att kolla hur långt man kommer med OOP inom C. Hehe.

Permalänk
Datavetare
Skrivet av heretic16:

Jag har ingen erfarenhet av Rust så jag vet inte. Men jag tror inte Rust kommer ersätta C++ eller C. Orsaken har med ISO standarder att göra. C verkar vara det språk som är bäst att skapa en standard med, trots att C++ är ett bättre språk funktionsmässigt.

Orsaken varför jag tycker att C++ är mer avancerat än C har med just OOP. Ibland när jag läser C++ kod så förstår jag inget.
Javakod är samma sak. De flesta skriver som krattor. Då menar jag inte att dom är dålig på att skriva kod, utan dom skriver bara kod som dom själva förstår. Någon gömd klass hit och dit och helt plötsligt en ny arbetsmetodik och struktur på projektet.

C18 är senaste standarden. Men C18 och C99 skiljer sig knappt. Något extra macro eller så.

Jag tror att en viss kunskap inom OOP inom C är aldrig fel. Bara så att man får funktionen. Sedan om den saknar enkapsulering eller generics. Det kvittar för min del.

Det är därför jag söker denna kunskap. Du får gärna visa mig vad man kan göra bättre än min senaste kod ovan Bara för kul. Alltid roligt att gräva i djupet för att kolla hur långt man kommer med OOP inom C. Hehe.

C18 är i praktiken en buggfix release av C11, skillnaderna är minimala. C11 och C99 skiljer sig rätt rejält i standardbiblioteket (har varit med och implementerat standardbiblioteket för C11 och C++11/14/17 för VxWorks).

I C2x kommer man lyfta in de nyheter från senare C++ standarder som "make sense" i C. Lär inte bli någon större förändring, gissar på mindre än C11 som faktiskt är relativt stor förändring.

Orkar inte fixa till en C-version av din kod, får räcka med de ~20 år jag jobbar på OS-kärnor och då primärt i C... Finns en del skrivet om OOP i C. Har läst ett "paper" som jag tyckte var riktigt bar, ska se om det går att Googla fram. Hittade denna
https://www.state-machine.com/doc/AN_OOP_in_C.pdf
där man rätt mycket gör "för hand" vad en C++ kompilator gör automatiskt för metoder som är "virtual".

Men ska se om jag orkar få till en Rust-motsvarighet, själv är jag hoppfull inför att det språket tar fart då det näst intill eliminerar en av de absolut dyraste typen av fel folk gör gång, på gång, på gång i språk som C, C++, C# och Java.

Det samtidigt som Rust prestandamässigt är helt i nivå med C++. Rust tillsammans med C, C++ och Swift är exempel på de rätt få systemspråk vi har idag med deterministiskt beteende m.a.p minneshantering, Swift har dock inte riktigt samma prestanda även om det är hyfsat nära.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av Yoshman:

C18 är i praktiken en buggfix release av C11, skillnaderna är minimala. C11 och C99 skiljer sig rätt rejält i standardbiblioteket (har varit med och implementerat standardbiblioteket för C11 och C++11/14/17 för VxWorks).

I C2x kommer man lyfta in de nyheter från senare C++ standarder som "make sense" i C. Lär inte bli någon större förändring, gissar på mindre än C11 som faktiskt är relativt stor förändring.

Orkar inte fixa till en C-version av din kod, får räcka med de ~20 år jag jobbar på OS-kärnor och då primärt i C... Finns en del skrivet om OOP i C. Har läst ett "paper" som jag tyckte var riktigt bar, ska se om det går att Googla fram. Hittade denna
https://www.state-machine.com/doc/AN_OOP_in_C.pdf
där man rätt mycket gör "för hand" vad en C++ kompilator gör automatiskt för metoder som är "virtual".

Men ska se om jag orkar få till en Rust-motsvarighet, själv är jag hoppfull inför att det språket tar fart då det näst intill eliminerar en av de absolut dyraste typen av fel folk gör gång, på gång, på gång i språk som C, C++, C# och Java.

Det samtidigt som Rust prestandamässigt är helt i nivå med C++. Rust tillsammans med C, C++ och Swift är exempel på de rätt få systemspråk vi har idag med deterministiskt beteende m.a.p minneshantering, Swift har dock inte riktigt samma prestanda även om det är hyfsat nära.

Make sense? Exempel?

Detta papper var bra. Väldigt bra.
Men koden är ju exakt det jag gör ovan. Arv + poly.

Jag vet inte vad det är för dyra fel man gör i dessa språk.
Själv råkar jag alltid ut för null i Java. Jag är riktigt less på exceptions. Jag önskar att lombok blev en inbyggd standard i Java 17 så man slipper skriva getters & setters. Jag önskar även att Java kunde ha rena funktioner.

Tror inte Rust kommer till inbyggda system. Assembler är då mer effektivt.

Permalänk
Datavetare

#[derive(Debug)] struct HuvudKlass{ huvud_variabel: i32, } #[derive(Debug)] struct MinKlass{ huvud: HuvudKlass, min_variabel: i32, } impl HuvudKlass{ fn get_huvud_variabel(&self) -> i32 { self.huvud_variabel } fn set_huvud_variabel(&mut self, val: i32) { self.huvud_variabel = val } } impl MinKlass{ fn get_min_variabel(&self) -> i32 { self.min_variabel } fn set_min_variabel(&mut self, val: i32) { self.min_variabel = val } } fn main() { let min_klass1 = MinKlass{ huvud: HuvudKlass{ huvud_variabel: 104 }, min_variabel: 200 }; let min_klass2 = MinKlass{ huvud: HuvudKlass{ huvud_variabel: 23 }, min_variabel: 500 }; let min_variabel_1 = min_klass1.get_min_variabel(); let min_variabel_2 = min_klass2.get_min_variabel(); let huvud_variabel_1 = min_klass1.huvud.get_huvud_variabel(); let huvud_variabel_2 = min_klass2.huvud.get_huvud_variabel(); println!("min_variabel_1 = {}, min_variabel_2 = {}, huvud_variabel_1 = {}, huvud_variabel_2 = {}", min_variabel_1, min_variabel_2, huvud_variabel_1, huvud_variabel_2); }

Varför Rust både kan vara säkrare och samtidigt snabbare än C: felet med OOP är att man blandar ihop koncepten "data" med "beteenden".

Moderna CPUer vill väldigt gärna ha data konsekutivt (vilket språk med GC ofta har stora problem att ordna) samtidigt som all form av indirekt anrop ställer till det då CPU måste "gissa" var hopp tar vägen. Motsvarigheten till din C kod i Rust läger data "på rad" samtidigt som som alla anrop kan göras direkta (i stället för indirekta via funktionspekare).

Ibland måste man ha "late-binding", det stöds naturligtvis också. Fördelen i både C++ och Rust är att deras syntax gör det möjligt för moderna kompilatorer som GCC och Clang att analysera kod och i vissa lägen "inse" att indirekta hopp faktiskt bara kan gå till ett specifikt ställe. I dessa lägen kommer kompilatorn göra om hoppen till direkta -> högre prestanda och mindre påverkan på cache (så win-win prestandamässigt).

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av Yoshman:

#[derive(Debug)] struct HuvudKlass{ huvud_variabel: i32, } #[derive(Debug)] struct MinKlass{ huvud: HuvudKlass, min_variabel: i32, } impl HuvudKlass{ fn get_huvud_variabel(&self) -> i32 { self.huvud_variabel } fn set_huvud_variabel(&mut self, val: i32) { self.huvud_variabel = val } } impl MinKlass{ fn get_min_variabel(&self) -> i32 { self.min_variabel } fn set_min_variabel(&mut self, val: i32) { self.min_variabel = val } } fn main() { let min_klass1 = MinKlass{ huvud: HuvudKlass{ huvud_variabel: 104 }, min_variabel: 200 }; let min_klass2 = MinKlass{ huvud: HuvudKlass{ huvud_variabel: 23 }, min_variabel: 500 }; let min_variabel_1 = min_klass1.get_min_variabel(); let min_variabel_2 = min_klass2.get_min_variabel(); let huvud_variabel_1 = min_klass1.huvud.get_huvud_variabel(); let huvud_variabel_2 = min_klass2.huvud.get_huvud_variabel(); println!("min_variabel_1 = {}, min_variabel_2 = {}, huvud_variabel_1 = {}, huvud_variabel_2 = {}", min_variabel_1, min_variabel_2, huvud_variabel_1, huvud_variabel_2); }

Varför Rust både kan vara säkrare och samtidigt snabbare än C: felet med OOP är att man blandar ihop koncepten "data" med "beteenden".

Moderna CPUer vill väldigt gärna ha data konsekutivt (vilket språk med GC ofta har stora problem att ordna) samtidigt som all form av indirekt anrop ställer till det då CPU måste "gissa" var hopp tar vägen. Motsvarigheten till din C kod i Rust läger data "på rad" samtidigt som som alla anrop kan göras direkta (i stället för indirekta via funktionspekare).

Ibland måste man ha "late-binding", det stöds naturligtvis också. Fördelen i både C++ och Rust är att deras syntax gör det möjligt för moderna kompilatorer som GCC och Clang att analysera kod och i vissa lägen "inse" att indirekta hopp faktiskt bara kan gå till ett specifikt ställe. I dessa lägen kommer kompilatorn göra om hoppen till direkta -> högre prestanda och mindre påverkan på cache (så win-win prestandamässigt).

Koden ser bra ut.
Men jag tror inte de stora företagen kommer välja Rust före C. Möjligtvis någon progressiv programmerare i Kalifornien väljer Rust före C++.

Texas Instrument och Microchip kör fortfarande med C och jag vet om att Macgreggor kör fortfarande med C89, trots att dom har möjlighet att välja något annat.

Jag tror att det är en ovilja att göra något annat. Man kör på något som fungerar tills ekonomin vägleder.

Jag såg att man kan göra Web Assembly i Rust. Detta var intressant.

Permalänk
Datavetare
Skrivet av heretic16:

Koden ser bra ut.
Men jag tror inte de stora företagen kommer välja Rust före C. Möjligtvis någon progressiv programmerare i Kalifornien väljer Rust före C++.

Texas Instrument och Microchip kör fortfarande med C och jag vet om att Macgreggor kör fortfarande med C89, trots att dom har möjlighet att välja något annat.

Jag tror att det är en ovilja att göra något annat. Man kör på något som fungerar tills ekonomin vägleder.

Jag såg att man kan göra Web Assembly i Rust. Detta var intressant.

Rust är inte speciellt välanvänt och finns inga garantier för att de någonsin skulle bli i nivå med C och C++.

Men det har ändå blivit framröstat till "mest älskade programspråk" på Stackoverflow varje år sedan 2016, senaste mätningen vann det med rätt bred marginal. Så de som tagit sig tid att förstå storheten verkar uppskatta språket.

Sedan finns det användning redan idag hos företag som Facebook (de skrev om en sökmotor från Python till Rust), Cloudflare (ersatt regelmotor skriven i C som står ut mot Internet, inte så bra med minnesbuggar där...), Amazon använder det i AWS (i tjänster som Lambda, EC2, and S3), Dropbox använder det i deras motor för synkronisering samt NPM (den mest använda pakethanteraren för JavaScript) skrev om deras back-end i Rust (efter ha ratat bl.a. C, C++ och Java av lite olika skäl).

Microsoft tittar på att göra det möjligt att skriva om kritiska delar i deras massiva C/C++ kodbas i Rust eller något liknande.

Både TI och Microchip verkar basera mycket av deras aktuella produkter på Arm Cortex M serien och de skriver att LLVM är deras primära kompilator-ramverk. LLVM är också vad Rust använder som kompilator-ramverk, så alla back-ends som fungerar för C/C++ under LLVM fungerar i praktiken nästan alltid i Rust. Just Arm Cortex M (och även AVR som används i t.ex. Arduino) har officiellt stöd i Rust.

Folk använder redan Rust just på TI och Microchip produkter. VxWorks, vilket är världens största RTOS, har officiellt stöd för Rust (jag var en av de som jobbade med det stödet).

Det är möjligt att skriva Web Assembly i andra språk, men för tillfället är just Rust den mest mogna miljön för detta. Och oavsett om Rust lyckas eller ej tror jag man med nära 100 % säkerhet kan säga att Web Assembly kommer bli en väldigt viktig teknik framöver!

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av Yoshman:

Rust är inte speciellt välanvänt och finns inga garantier för att de någonsin skulle bli i nivå med C och C++.

Men det har ändå blivit framröstat till "mest älskade programspråk" på Stackoverflow varje år sedan 2016, senaste mätningen vann det med rätt bred marginal. Så de som tagit sig tid att förstå storheten verkar uppskatta språket.

Sedan finns det användning redan idag hos företag som Facebook (de skrev om en sökmotor från Python till Rust), Cloudflare (ersatt regelmotor skriven i C som står ut mot Internet, inte så bra med minnesbuggar där...), Amazon använder det i AWS (i tjänster som Lambda, EC2, and S3), Dropbox använder det i deras motor för synkronisering samt NPM (den mest använda pakethanteraren för JavaScript) skrev om deras back-end i Rust (efter ha ratat bl.a. C, C++ och Java av lite olika skäl).

Microsoft tittar på att göra det möjligt att skriva om kritiska delar i deras massiva C/C++ kodbas i Rust eller något liknande.

Både TI och Microchip verkar basera mycket av deras aktuella produkter på Arm Cortex M serien och de skriver att LLVM är deras primära kompilator-ramverk. LLVM är också vad Rust använder som kompilator-ramverk, så alla back-ends som fungerar för C/C++ under LLVM fungerar i praktiken nästan alltid i Rust. Just Arm Cortex M (och även AVR som används i t.ex. Arduino) har officiellt stöd i Rust.

Folk använder redan Rust just på TI och Microchip produkter. VxWorks, vilket är världens största RTOS, har officiellt stöd för Rust (jag var en av de som jobbade med det stödet).

Det är möjligt att skriva Web Assembly i andra språk, men för tillfället är just Rust den mest mogna miljön för detta. Och oavsett om Rust lyckas eller ej tror jag man med nära 100 % säkerhet kan säga att Web Assembly kommer bli en väldigt viktig teknik framöver!

Möjligvis att Rust ersätter C++ när det kommer till skrivbordsapplikationer.
Men för inbyggda system eller hårdvaru-API:er så tvivlar jag på att Rust kommer ersätta C.

Jag tror att Rust är ytterligare ett språk som lever på att den är i opposition.

Permalänk
Medlem

Jag förstår personligen inte varför man skulle vilja utsätta sig för OOP men om man nu vill det och man valt C som språk så kan jag tipsa om glib och GObject (open source, multi-platform). Som en del av projektet så finns det generator-binärer som genererar kodsnippets från en objektbeskrivning i XML så man slipper skriva boilerplate-koden själv.

https://developer.gnome.org/gobject/stable/

Permalänk
Medlem
Skrivet av heretic16:

Möjligvis att Rust ersätter C++ när det kommer till skrivbordsapplikationer.
Men för inbyggda system eller hårdvaru-API:er så tvivlar jag på att Rust kommer ersätta C.

Jag tror att Rust är ytterligare ett språk som lever på att den är i opposition.

Jag vill bara påpeka att vi alla kan bara spekulera om framtiden. (Yoshman har av allt att döma dock en smula mer erfarenhet i området.) Nåväl, det viktiga (i mitt tycke, som också jobbat i branschen i 10+ år) från det Yoshman skriver ovan är just ang. "data" vs "beteende".

Skrivet av orp:

Jag förstår personligen inte varför man skulle vilja utsätta sig för OOP men om man nu vill det och man valt C som språk så kan jag tipsa om glib och GObject (open source, multi-platform). Som en del av projektet så finns det generator-binärer som genererar kodsnippets från en objektbeskrivning i XML så man slipper skriva boilerplate-koden själv.
...

Har man utsatts för GLib/GObject och den härdsmälta det bildar så önskar man inget hellre än att få det ogjort. Tipset är att undvika att skjuta dig själv i foten.

Visa signatur

Citera mig för svar.
Arch Linux

Permalänk
Medlem
Skrivet av Dimman:

Har man utsatts för GLib/GObject och den härdsmälta det bildar så önskar man inget hellre än att få det ogjort. Tipset är att undvika att skjuta dig själv i foten.

Det var ju inte särskilt konstruktivt. Jag skulle ju föredra GObject framför att skriva en eget OOP-ramverk i C. Som jag sa förstår inte varför man ens skulle vilja beblanda sig med OOP från första början men alla har sina anledningar.

Permalänk
Medlem
Skrivet av orp:

Det var ju inte särskilt konstruktivt. Jag skulle ju föredra GObject framför att skriva en eget OOP-ramverk i C. Som jag sa förstår inte varför man ens skulle vilja beblanda sig med OOP från första början men alla har sina anledningar.

Sorry, det blev lite otydligt. Inlägget var inte menat till dig utan till TS Jag håller med dig, även om jag sannolikt valt ett annat OOP-ramverk om jag varit tvingad att använda ett i C.

(Off topic: Inser nu att vi till och med kan vara gamla kollegor från ett ställe där vi båda stött på GLib/GObject mer än önskat )

Visa signatur

Citera mig för svar.
Arch Linux

Permalänk
Medlem
Skrivet av Dimman:

Sorry, det blev lite otydligt. Inlägget var inte menat till dig utan till TS Jag håller med dig, även om jag sannolikt valt ett annat OOP-ramverk om jag varit tvingad att använda ett i C.

(Off topic: Inser nu att vi till och med kan vara gamla kollegor från ett ställe där vi båda stött på GLib/GObject mer än önskat )

Ingen fara, jag känner inte till fler OOP-ramverk till C och tänker inte leta efter några.

(Off topic: Vad skoj! Har starka misstankar om vem du är med tanke på Arch i signaturen )

Permalänk
Datavetare

@heretic16

Lyckades snubbla över den beskrivning av hur man kan lösa typ allt kring OOP i standard ANSI C jag nämnde tidigare
https://www.cs.rit.edu/~ats/books/ooc.pdf

Tycker man kan se den ur två perspektiv

Glaset halvfullt: ändå rätt cool hur de implementera saker som data hiding och reflection i ett språk som aldrig designats för OOP.

Glaset halvtomt: rätt uppenbart vilka stora brister C har i OOP (utöver de rätt stora brister OOP självt har som teknik utanför ett par rätt smala nischer där det är helt rätt teknik). T.ex. får man i praktiken välja en av

  • data hiding (vilket slänger ut de flesta former av statisk typ-check genom fönstret -> dåligt då färre fel fångas vid kompilering)

  • möjlighet till statisk allokering av objekt

  • någon form av extern meta-kompilatorn som i praktiken gör det helt poänglöst att använda C då det inte längre är ANSI/ISO C

Här är ännu ett exempel där C++ (och för den delen Rust) i praktiken kommer vara mer effektivt än C, där finns inget motsatsförhållande mellan ge data hiding och möjlighet till statisk allokering av objekt då språket i sig har det stöds som krävs för att erbjuda detta.

Frågan i titeln till denna tråd är "Är det värt att komplettera grundläggande OOP i C, i stället för att lära sig C++". Svaret är ett rungande NEJ. Enda orsaken att plåga sig med OOP i C är för att man absolut inte har några alternativ

Exempel när man inte har alternativ är om man idag skriver kernel-kod till Linux och Windows, andra OS som t.ex. MacOS (C++) och VxWorks (C++ och Rust) tillåter andra språk i kärnan!

Men både Windows och Linux tittar på att bl.a. göra det möjligt att använda Rust för drivers. C++ lär aldrig komma in i Linus Torvalds Linux träd, han har väldigt mycket emot C++ som språk i OS-kärna (men har verkar fatta fördelarna med Rust).

Fast tycker nog Fuchsia visat att även någon så smart som Linus ibland kan vara helt ute och cykla. Fuchsia kärnan använder C++ i sin kernel och att läsa den koden är en dröm jämfört med de C kärnor jag jobbat med! Apples kernel-system där man tillåter C++ är i min mening också betydligt trevligare/enklare att jobba med jämfört med motsvarande i Windows/Linux.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av orp:

Jag förstår personligen inte varför man skulle vilja utsätta sig för OOP men om man nu vill det och man valt C som språk så kan jag tipsa om glib och GObject (open source, multi-platform). Som en del av projektet så finns det generator-binärer som genererar kodsnippets från en objektbeskrivning i XML så man slipper skriva boilerplate-koden själv.

https://developer.gnome.org/gobject/stable/

Skrivet av Dimman:

Jag vill bara påpeka att vi alla kan bara spekulera om framtiden. (Yoshman har av allt att döma dock en smula mer erfarenhet i området.) Nåväl, det viktiga (i mitt tycke, som också jobbat i branschen i 10+ år) från det Yoshman skriver ovan är just ang. "data" vs "beteende".

Har man utsatts för GLib/GObject och den härdsmälta det bildar så önskar man inget hellre än att få det ogjort. Tipset är att undvika att skjuta dig själv i foten.

Skrivet av orp:

Det var ju inte särskilt konstruktivt. Jag skulle ju föredra GObject framför att skriva en eget OOP-ramverk i C. Som jag sa förstår inte varför man ens skulle vilja beblanda sig med OOP från första början men alla har sina anledningar.

Skrivet av Dimman:

Sorry, det blev lite otydligt. Inlägget var inte menat till dig utan till TS Jag håller med dig, även om jag sannolikt valt ett annat OOP-ramverk om jag varit tvingad att använda ett i C.

(Off topic: Inser nu att vi till och med kan vara gamla kollegor från ett ställe där vi båda stött på GLib/GObject mer än önskat )

Jag känner inte till detta bibliotek. Men det verkar vara mer värt att anvöbda detta än att skriva OOP i C.

Så kan man dra slutsatsen att om man vill komplettera mer OOP så är det bättre att använda detta bibliitek eller Cello?

Permalänk
Skrivet av Yoshman:

@heretic16

Lyckades snubbla över den beskrivning av hur man kan lösa typ allt kring OOP i standard ANSI C jag nämnde tidigare
https://www.cs.rit.edu/~ats/books/ooc.pdf

Tycker man kan se den ur två perspektiv

Glaset halvfullt: ändå rätt cool hur de implementera saker som data hiding och reflection i ett språk som aldrig designats för OOP.

Glaset halvtomt: rätt uppenbart vilka stora brister C har i OOP (utöver de rätt stora brister OOP självt har som teknik utanför ett par rätt smala nischer där det är helt rätt teknik). T.ex. får man i praktiken välja en av

  • data hiding (vilket slänger ut de flesta former av statisk typ-check genom fönstret -> dåligt då färre fel fångas vid kompilering)

  • möjlighet till statisk allokering av objekt

  • någon form av extern meta-kompilatorn som i praktiken gör det helt poänglöst att använda C då det inte längre är ANSI/ISO C

Här är ännu ett exempel där C++ (och för den delen Rust) i praktiken kommer vara mer effektivt än C, där finns inget motsatsförhållande mellan ge data hiding och möjlighet till statisk allokering av objekt då språket i sig har det stöds som krävs för att erbjuda detta.

Frågan i titeln till denna tråd är "Är det värt att komplettera grundläggande OOP i C, i stället för att lära sig C++". Svaret är ett rungande NEJ. Enda orsaken att plåga sig med OOP i C är för att man absolut inte har några alternativ

Dålig PDF om hur man gör OOP i C. Verkar grötigt.
Men rekommenderar du inte att man tar till sig lite OOP i C? Alltså enkel grund? Typ som jag har gjort. Man kombinerar funktionspekare i strukturer?

Citat:

Exempel när man inte har alternativ är om man idag skriver kernel-kod till Linux och Windows, andra OS som t.ex. MacOS (C++) och VxWorks (C++ och Rust) tillåter andra språk i kärnan!

Men både Windows och Linux tittar på att bl.a. göra det möjligt att använda Rust för drivers. C++ lär aldrig komma in i Linus Torvalds Linux träd, han har väldigt mycket emot C++ som språk i OS-kärna (men har verkar fatta fördelarna med Rust).

Fast tycker nog Fuchsia visat att även någon så smart som Linus ibland kan vara helt ute och cykla. Fuchsia kärnan använder C++ i sin kernel och att läsa den koden är en dröm jämfört med de C kärnor jag jobbat med! Apples kernel-system där man tillåter C++ är i min mening också betydligt trevligare/enklare att jobba med jämfört med motsvarande i Windows/Linux.

Inget kommer ersätta C än. Rust må vara ett alternativ på C++ nivå. Men som vanligt så är Rust som alla andra språk som försöker döda datapionnärernas språk. Språket D skulle ersätta C++ men det gick inte direkt så. Rust kommer gå samma väg.

Orsaken är att C++ ändrar sig eftee behov.

Men när det kommer till C så finns det idag inget som lockar det stora drakarna att välja rust framför C. Jag ser aldrig Rust ute på industrin. C är allt som gäller.

Permalänk
Medlem
Skrivet av heretic16:

Jag känner inte till detta bibliotek. Men det verkar vara mer värt att anvöbda detta än att skriva OOP i C.

Så kan man dra slutsatsen att om man vill komplettera mer OOP så är det bättre att använda detta bibliitek eller Cello?

Får jag fråga vad det är du gör som kräver C-prestanda? Får inte intrycket av att du vill bygga ett eget operativsystem eller nått i den nivån så om du vill ha OOP så finns roliga moderna språk som Kotlin eller Scala som har stöd för både OOP och funktionella stilen om man hellre vill köra så, även JS/TS nu för tiden även fast det kanske är lite svagare på funktionella delen med t ex tail recursion om jag inte missminner mig.

Säger inte att C inte kan vara roligt, undrar bara hur resonemanget går

Angående Rust så är det som nämnt framröstat som most loved på SO för femte året i rad, blir säkert det i år också så personligen tror jag att om 10 år är det väldigt få som väljer något annat när de väljer ett språk i den kategorin för nyutveckling. Den som lever får se.

Permalänk
Skrivet av Xenofonus:

Får jag fråga vad det är du gör som kräver C-prestanda? Får inte intrycket av att du vill bygga ett eget operativsystem eller nått i den nivån så om du vill ha OOP så finns roliga moderna språk som Kotlin eller Scala som har stöd för både OOP och funktionella stilen om man hellre vill köra så, även JS/TS nu för tiden även fast det kanske är lite svagare på funktionella delen med t ex tail recursion om jag inte missminner mig.

Säger inte att C inte kan vara roligt, undrar bara hur resonemanget går

Angående Rust så är det som nämnt framröstat som most loved på SO för femte året i rad, blir säkert det i år också så personligen tror jag att om 10 år är det väldigt få som väljer något annat när de väljer ett språk i den kategorin för nyutveckling. Den som lever får se.

Jag vill bara bli bättre på C.
Mycket av Java-kod jag läser kan jag inte förstå. De skriver som överdrivna experter.

Så jag tänkte att C ksnske blir likadant. Svårt att läsa om man inte kan grund OOP i C.

Permalänk
Hedersmedlem
Skrivet av heretic16:

Så jag tänkte att C ksnske blir likadant. Svårt att läsa om man inte kan grund OOP i C.

Man skall nog inte räkna med att c kommer ändra sig särskilt mycket under överskådlig framtid.

Permalänk
Datavetare
Skrivet av heretic16:

Dålig PDF om hur man gör OOP i C. Verkar grötigt.
Men rekommenderar du inte att man tar till sig lite OOP i C? Alltså enkel grund? Typ som jag har gjort. Man kombinerar funktionspekare i strukturer?

"Verkar grötig"? Har du läst den?

OOP vilar på fyra ben

  • abstraktion: gömma detaljer bakom funktioner, "gör kaffe" i stället för "lägg bönor i kvarnen, mal, värm vatten..."

  • inkapsling: kombinera data med de metoder som har kunskapen om att jobba med data

  • arv: möjlighet att definiera data/beteende för generella egenskaper som följer med till specialiseringar

  • polymorfism: ge specialiseringar unika egenskaper utan att den som bara bryr sig om det gemensamma gränssnittet behöver bry sig om detaljer

I isolation är ingen av dessa på något sätt unikt för OOP, alla vettig icke-triviala program måste jobba med abstraktioner

Möjligen kan inkapsling ses som rätt OOP unikt, men det är också en av den mest ifrågasatta egenskapen. Många nya språk pekar specifikt ut fördelarna med att ha en klar separation mellan data från logik som utför transformer på data. I mulitrådade program är inkapsling ett rejält hinder då effektiv och korrekt hantering av data som kan skrivas från mer än en OS-tråd kräver att data är explicit synlig.

Språk som Clojure har enormt flexibel hantering av polymorfism, men tror ingen skulle kalla det för ett "OOP" språk.

Ditt exempel som inleder tråden har ingen direkt bra lösning för arv och den saknar helt inkapsling. PDF:en visar hur alla de fyra egenskaperna ovan kan implementeras i C, tyvärr blir resultatet en rätt sorglig historia...

Tycker http://libcello.org som nämns ovan verkar fungera väldigt likt vad som beskrivs i PDF:en. Fast tänker du använda Cello? De skriver ju själva:

"Is anyone using Cello? People have experimented with it, but there is no high profile project I know of that uses it. Cello is too big and scary a dependency for new C projects if they want to be portable and easy to maintain."

Skrivet av heretic16:

Inget kommer ersätta C än. Rust må vara ett alternativ på C++ nivå. Men som vanligt så är Rust som alla andra språk som försöker döda datapionnärernas språk. Språket D skulle ersätta C++ men det gick inte direkt så. Rust kommer gå samma väg.

Orsaken är att C++ ändrar sig eftee behov.

Men när det kommer till C så finns det idag inget som lockar det stora drakarna att välja rust framför C. Jag ser aldrig Rust ute på industrin. C är allt som gäller.

Så varför använder du inte C++ i stället då? Där är alla problem som diskuterats här redan löst i språkdesignen!

Och en väldigt trevlig egenskap hos C++ är att det aldrig (i motsats till Java och i mindre utsträckning C#) tvingat in allt i klasser. Där har man alltid haft åsikten att OOP är bra för vissa specifika saker men bör lämnas utanför det mesta. Tittar man på standardbiblioteket för C++ så lyser OOP nästan helt med sin frånvaro.

Du pratar också av vikten av effektivitet. Har gett ett konkret exempel på hur C++ kan vara väsentligt mer effektivt jämfört med C (och i det fallet finns egentligen inget man kan göra inom ramen för "vanlig" C för att matcha C++).

Här är ett annat exempel där både C++ men även Rust är klart snabbare än C (då det likt C++ har kontext nog att helt eliminera alla former av indirekta hopp). I detta fall är det fullt möjligt att hand-jaga en C-variant som är lika snabb, men det blir långt mer komplicerat och fungerar då i praktiken bara för en enda typ medan C++/Rust varianten fungerar för alla typer som där element har en inbördes ordning.

Bygger man utan optimeringar blev C varianten snabbare för mig, fast när man slår på optimeringar vad C++ ~60 % snabbare på en RPi4 och ~40 % snabbare på M1/Zen3/Skylake.

För den som vill se koden / testa själv: https://github.com/kjn-void/sort_bench

Om någon kör Windows, endera kan ni CMake eller så kör WSL2...

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av Yoshman:

"Verkar grötig"? Har du läst den?

OOP vilar på fyra ben

  • abstraktion: gömma detaljer bakom funktioner, "gör kaffe" i stället för "lägg bönor i kvarnen, mal, värm vatten..."

  • inkapsling: kombinera data med de metoder som har kunskapen om att jobba med data

  • arv: möjlighet att definiera data/beteende för generella egenskaper som följer med till specialiseringar

  • polymorfism: ge specialiseringar unika egenskaper utan att den som bara bryr sig om det gemensamma gränssnittet behöver bry sig om detaljer

I isolation är ingen av dessa på något sätt unikt för OOP, alla vettig icke-triviala program måste jobba med abstraktioner

Möjligen kan inkapsling ses som rätt OOP unikt, men det är också en av den mest ifrågasatta egenskapen. Många nya språk pekar specifikt ut fördelarna med att ha en klar separation mellan data från logik som utför transformer på data. I mulitrådade program är inkapsling ett rejält hinder då effektiv och korrekt hantering av data som kan skrivas från mer än en OS-tråd kräver att data är explicit synlig.

Språk som Clojure har enormt flexibel hantering av polymorfism, men tror ingen skulle kalla det för ett "OOP" språk.

Ditt exempel som inleder tråden har ingen direkt bra lösning för arv och den saknar helt inkapsling. PDF:en visar hur alla de fyra egenskaperna ovan kan implementeras i C, tyvärr blir resultatet en rätt sorglig historia...

Jag tycker PDF:en var rörig jämfört med den första PDF:en du skickande. Allt är ju relativt.

Citat:

Tycker http://libcello.org som nämns ovan verkar fungera väldigt likt vad som beskrivs i PDF:en. Fast tänker du använda Cello? De skriver ju själva:

"Is anyone using Cello? People have experimented with it, but there is no high profile project I know of that uses it. Cello is too big and scary a dependency for new C projects if they want to be portable and easy to maintain."

Så varför använder du inte C++ i stället då? Där är alla problem som diskuterats här redan löst i språkdesignen!

Och en väldigt trevlig egenskap hos C++ är att det aldrig (i motsats till Java och i mindre utsträckning C#) tvingat in allt i klasser. Där har man alltid haft åsikten att OOP är bra för vissa specifika saker men bör lämnas utanför det mesta. Tittar man på standardbiblioteket för C++ så lyser OOP nästan helt med sin frånvaro.

Du pratar också av vikten av effektivitet. Har gett ett konkret exempel på hur C++ kan vara väsentligt mer effektivt jämfört med C (och i det fallet finns egentligen inget man kan göra inom ramen för "vanlig" C för att matcha C++).

Självklart är C++ bättre vid OOP än C. Men jag tänkte bara kolla om det finns ett behov utav funktionspekare i strukturer. Bara för att få till en liten enkel OOP-funktion som man gjorde förr på DOOM och Quake spelen.

Citat:

Här är ett annat exempel där både C++ men även Rust är klart snabbare än C (då det likt C++ har kontext nog att helt eliminera alla former av indirekta hopp). I detta fall är det fullt möjligt att hand-jaga en C-variant som är lika snabb, men det blir långt mer komplicerat och fungerar då i praktiken bara för en enda typ medan C++/Rust varianten fungerar för alla typer som där element har en inbördes ordning.

Bygger man utan optimeringar blev C varianten snabbare för mig, fast när man slår på optimeringar vad C++ ~60 % snabbare på en RPi4 och ~40 % snabbare på M1/Zen3/Skylake.

För den som vill se koden / testa själv: https://github.com/kjn-void/sort_bench

Om någon kör Windows, endera kan ni CMake eller så kör WSL2...

Jag tror fortfarande att Rust är inte framtiden. Varför?
Rust är säkert ett jätte bra språk. Men dessa fina och goda språk dyker upp hela tiden. Det är bara en tidsfråga innan t.ex. Facebook uppfinner ett nytt språk som anses vara bättre än Rust och då minskar Rust's användare där.
Kvar blir C++ som uppdaterar sitt språk under tystnad.

Det är inte troligt att förlita sig på några enkla algoritmer för att jämföra C mot C++. Ungefär som fysiker endast befinner sig i linjära dynamiska (matematik) tillstånd för att beräkna på sina formler, utan att ta hänsyn till verkligheten. Jag har kört program gjorda i C och C++ och i praktiken så är dessa program som är gjorda i C, mycket snabbare än program som är gjorda i C++. Jag tycker detta kriteriet räknas.

Inte "Vilket språk gör sortering snabbast?". Detta är irrelevant.