Permalänk

C++ arv och polymorphism

Tjena,

Jag är en självlärd programmerare och har lite frågor kring arv och polymorphism.

Jag har inget specifikt kodexempel för metoden jag använt fungerar, men de kodexempel jag har tittat på för att lära mig ifrån har använt en annan metod men uppnått samma resultat vilket inte är helt ovanligt inom programmering, men alla exempel hade gjort likadant kring just detta så det handlar nog om standarder inom språket och det är även min fråga. Nedan skriver jag med metoden jag använt och sedan metoden exemplen jag lärt mig från har använt

Jag skapade en föräldrar klass som i princip agerar som en övergripande mall som barn klasserna använder sig av.
Exempelvis föräldrar klassen Enhet och sen barn klasser som kan bestå av enhetstyper. Jag vet att alla enheter kommer behöva positions värden så det jag gjorde var att deklarera variabler i föräldrar klassen men sedan initierar dem i barn klassen och det fungerar.
Exempel jag hade följt gjorde istället så att de skapade virtuella funktioner med returvärden av variabel typen och sedan deklarerade och initiera dem i barn klassen.

Är det en standard man använder och något jag missat? Det verkar göra koden mer lättläslig att deklarera och initiera dem i barn klassen.

Skulle det vara någon som förstår sig på mitt rabbel och kan tänka sig svara med en förklaring skulle det uppskattas otroligt!

Permalänk
Medlem

Hmm, inte helt säker på att jag hänger med på vad du menar, men det man vanligen brukar göra är att anropa föräldraklassens konstruktor i barnklasserna:

class Base { public: Base(int someVar) : _someVar(someVar) {} protected: int _someVar; }; class Derived : public Base { public: Derived(int someVar, int someOtherVar) : Base(someVar), _someOtherVar(someOtherVar) {} protected: int _someOtherVar; };

Eftersom Base i detta fall har en konstruktor som tar argument så är du tvingad att anropa den i alla Deriveds konstruktorer, så på detta sätt kan du inte missa att initiera variablerna i Base.

Permalänk
Hedersmedlem

Många gillar ju också att använda get/set-funktioner istället för publika medlemsvariabler; kanske är de virtuella funktionerna bara ett tecken på det?

Permalänk
Medlem

Det kan alltid finnas anledningar till att göra på olika sätt, men omedelbart kan jag inte se en bra anledning att göra som i exempel två där du skapar virtuella funktioner för get/set. Om basklassen deklarerar variabeln, så borde det också vara den klassen som har kunskap om vad variabeln är till för och därmed hur den ska initieras och eventuella restriktioner i set/get funktionerna.

Om det du vill göra är att definiera ett interface, så kallad pure virtual klass, så ska du inte ha medlemsvariabler alls i basklassen. Användningsområdet för interfaces är annorlunda än de klassiska arvsexemplena med Frukt -> Äpple osv. Ett interface är ett sätt definiera funktionalitet (och tvinga implementation därav) utan att specificera vilken algoritm som ska användas eller vilka variabler.

Så i korthet, om det är arv du är ute efter så initierar du i basklassen och i den ärvda klassen (om användandet/värdet av variabeln skiljer sig från basklassen). Om det är interface du vill använda så definieras och initieras variablerna i den ärvda klassen.

Visa signatur

Louqe Ghost S1 MK3 | Asus ROG Strix B660-I Gaming WiFi | Intel Core i7 12700K | nVidia RTX 2070 Super FE | Corsair 64GB (2x32GB) DDR5 5600MHz CL40 Vengeance | Samsung 980 PRO M.2 NVMe SSD 2TB | Corsair SF750 750W 80+ Platinum | Noctua NH-L12 Ghost S1 edition | Kablar från pslate customs | 2 stk Dell Ultrasharp 3014 | Logitech MX Keys | Logitech MX Anywhere

Permalänk

Personligen tycker jag att man aldrig skall exponera medlemsvariabler utanför klassen. Om man sätter medlemsvariabler som protected i basklassen så läcker man ett internt tillstånd hos basklassen utåt och det gillar jag inte riktigt. Det finns fall där det skulle kunna vara "lite ok" och det är om medlemsvariablen är deklarerad const så att man bara läser den.

Om det är något som man vet att alla klasser av denna typ skall ha så tycker jag att man deklarerar det i basklassen och gör set och get funktioner för åtkomst.

Om det är något som alla klasser av denna typ har en egen version av. Då tycker jag man gör set och get funktioner som är pure virtual och tvingar de som ärver att implementera det.

Permalänk
Medlem
Skrivet av Superhepper:

Personligen tycker jag att man aldrig skall exponera medlemsvariabler utanför klassen. Om man sätter medlemsvariabler som protected i basklassen så läcker man ett internt tillstånd hos basklassen utåt och det gillar jag inte riktigt. Det finns fall där det skulle kunna vara "lite ok" och det är om medlemsvariablen är deklarerad const så att man bara läser den.

Om det är något som man vet att alla klasser av denna typ skall ha så tycker jag att man deklarerar det i basklassen och gör set och get funktioner för åtkomst.

Om det är något som alla klasser av denna typ har en egen version av. Då tycker jag man gör set och get funktioner som är pure virtual och tvingar de som ärver att implementera det.

Jag förstår din önskan om inkapsling, men som jag ser det så är det inget problem så länge du själv har kontroll på arvskedjan. Det är en annan sak om du ska exponera dina klasser genom ett API eller liknande, men då har man ändå ofta väldefinierade interface och andra krav som automatiskt innebär inkapsling (såsom binär bakåtkombabilitet som tvingar dig att ha medlemsvariabler i separata klasser).

Det kan också finnas komplikationer med att ha medlemsvariabler som private, t.ex. om du har behov av att initiera variablerna i konstruktorns initieringslista. Ska du göra detta med total inkapsling måste du ha konstruktorer i basklassen som kan ta initialvärden för alla dina medlemsvariabler, så att den ärvda klassen kan anropa basklassens konstruktor och sätta värden den vägen. Det blir snabbt bökigt och ointuitivt, speciellt om medlemsvariablerna är ett internt tillstånd som inte ska exponeras utåt. Användare av klassen (den som skapar instanser) får då möjlighet att initiera variabler som han/hon inte har något med att göra, vilket i sig strider mot kravet om inkapsling.

Jag vidhåller därför att medlemsvariabler som används i både basklassen och den ärvda klassen ska vara protected och inte private.

EDIT: Detta är diskussion naturligtvis och det skulle vara kul att få läsa lite "best practices" dokument från diverse företag för att se hur man löst saker som denna.

Visa signatur

Louqe Ghost S1 MK3 | Asus ROG Strix B660-I Gaming WiFi | Intel Core i7 12700K | nVidia RTX 2070 Super FE | Corsair 64GB (2x32GB) DDR5 5600MHz CL40 Vengeance | Samsung 980 PRO M.2 NVMe SSD 2TB | Corsair SF750 750W 80+ Platinum | Noctua NH-L12 Ghost S1 edition | Kablar från pslate customs | 2 stk Dell Ultrasharp 3014 | Logitech MX Keys | Logitech MX Anywhere

Permalänk
Medlem

Kom bara ihåg att virtuella metoder inte är gratis att anropa då de kostar några extra cykler jämfört med en vanlig metod. I många fall spelar det ingen roll, men ska metoderna användas ofta i t.ex. tighta loopar så kan det göra en avsevärd skillnad på prestandan.

Jag hade lagt dem som instansvariabler i parent-klassen och gett dem get/set metoder där. Virtuella metoder i child-klassen låter som att någon överkomplicerar det hela en smula.

Permalänk
Skrivet av sunefred:

Jag vidhåller därför att medlemsvariabler som används i både basklassen och den ärvda klassen ska vara protected och inte private.

Det skulle var intressant om du kunde ge ett exempel där en medlemsvariabel används och modifieras i både basklass och nedärvd klass. Kall mig trångsynt(hehe) men för mig så känns det bara som en felaktig design om man hamnar i ett läge där detta är nödvändigt.

Permalänk
Medlem
Skrivet av Superhepper:

Det skulle var intressant om du kunde ge ett exempel där en medlemsvariabel används och modifieras i både basklass och nedärvd klass. Kall mig trångsynt(hehe) men för mig så känns det bara som en felaktig design om man hamnar i ett läge där detta är nödvändigt.

Kanske det, men då är det väl talande att de flesta större projekt är felaktigt designade i så fall.

Några exempel där man använder en protected medlemsvariabel ifrån basklassen i nedärvd klass:

  • Qt

  • Torque 3D

  • Unreal Engine 3

  • Unreal Engine 4

  • Ogre 3D

Permalänk
Datavetare
Skrivet av Superhepper:

Det skulle var intressant om du kunde ge ett exempel där en medlemsvariabel används och modifieras i både basklass och nedärvd klass. Kall mig trångsynt(hehe) men för mig så känns det bara som en felaktig design om man hamnar i ett läge där detta är nödvändigt.

Underklasser har ju redan en väldigt intim relation med den klass de ärver så finns flera anledningar varför det inte kan anses något större problem att de direkt ser (vissa) datamedlemmar hos sina förändrar.

Om klassen någonsin kan tänkas användas i ett multitrådat program så är det ett krav att underklasser har direkt tillgång till all data de behöver för sin funktion. När man resonerar kring korrekthet i multitrådade program så måste man alltid utgå från den fysiska representationen av data och hur data är skyddat i fall där flera trådar samtidigt kan läsa/skriva samma data.

Då en av hörnstenarna i OOP är inkapsling så är ett rätt uppenbar slutsats att man bör ifrågasätta om OOP är rätt i en produkt som kan tänkas använda i multitrådade program. Är körs programmet även på multicore där effektivitet över kärnor är kritiskt borde rimligen OOP vara uteslutet alt. så använder man endast "immutable" objekt rakt igenom.

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 Curik:

Kanske det, men då är det väl talande att de flesta större projekt är felaktigt designade i så fall.

Jag tror att jag är alldeles för insöad i ett OOP tänk. Men om man designar något utifrån ett prestanda perspektiv så vill man naturligtvis minimera all onödig overhead. Och funktionskall för åtkomst till variabler är ju precis sådant som då kan tas bort. Så att kalla sådan design fel är inte en helt korrekt beskrivning.

Permalänk

Finessen med protected är just att man får access till basklassen medlemsvariabler. Det är klart att man skall använda dem direkt och inte larva sig med access-metoder. Man skall inte krångla till det i onödan.