Függvények priváttá tétele. Hogyan?

 ( pelz | 2008. április 12., szombat - 20:48 )

Sziasztok!

A kérdésem: a C++ nyelvben, hogyan lehet egy leszármazott osztályban az ősosztály néhány (nem minden!) tagfüggvényét átminősíteni private taggá? Ha átdeklaráltam, hogyan implementáljam?

Ezt a kódot írtam, de nem működik:

#include

class c1
{
public:
int fv1(int a, int b)
{
return a + b;
}
int fv2(int a, int b)
{
return a * b;
}
};

class c2 : public c1
{
public:
int fv3(int a, int b)
{
return fv1(a, b) + 10;
}
private:
int fv1(int a, int b);
};

int main()
{
c1 x;
c2 y;
std::cout << "\n\n" << x.fv1(1, 2) << " " << y.fv3(1, 2) << "\n\n";
}

Ha ezt írom:

private:
int fv1(int a, int b) { }

Akkor viszont nem csinál a fv1 semmit, mert nincs benne kód.
Kellene valami ilyesmi lehetőség:

private:
int fv1(int a, int b) { inherited fv1(a,b) }

Tehát kéne egy lehetőség arra, hogy végre is hajtsa osztályon belül a műveletet, de mégis priváttá váljon.

Előre is köszi a segítségeteket!

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Tedd [ code ] [ /code ] tagek koze.
<iostream> igy nem latszik.

Szia Turul|16!

Mit tegyek azok közzé? Mit írjak be pontosan?

[code]
Teljes forras kod

#include <iostream>
  ...
  ...

[/code]

Formazas is megmarad igy

Ja bocs!
Most már értem. Nem látszik az "iostream".

Tehát úgy kell elképzelni a kódot, hogy az #include kifejezés után a iostream következik!

Láthatóságot bővíteni lehet a származtatás során, csökkenteni nem (virtuális tagfüggvények esetén).

Tehát ha az ősosztályban valami protected volt, akkor a származtatottban már csak protected vagy public lehet, illetve public esetén csak public, és végül private esetén bármelyik.

A nem virtuális függvények esetén nincsen ilyen gond, csak az meg meg tud viccelni, hiszen ott a statikus (fordítási időben eldöntött) típus számít, pl. fv. paramétereknél.

Másik kérdésedre:

class Base
{
 
 public:
  void Fv ();
};


class Inherited : public base
{

 private:
   void Fv();

};

void Inherited::Fv()
{
  Base::Fv();
  // ...
}

Köszönöm Panther!

Bejött! Működik!

Igy:

#include <iostream>

class A {
public:
  int f1(int a, int b) { return a+b; }
};

class B : public A {
public:
  int f2(int a, int b) { return f1(a,b)+1; }
private:
  int f1(int a, int b) { return A::f1(a,b); }
};


int main() {
  B b;
  std::cout << b.f2(3,4) << std::endl;
  return 0;
}

Ez tervezési hiba. Nem a protected-et keresed?

Ilyen esetben private öröklődés lehet a megoldás, amit meg mégis el akarsz érni az ősosztályból, oda a származtatott osztályba is csinálsz egy ugyanolyan public tagfüggvényt, és meghívod belőle az ősosztályét.

Szia Zebra!

Ez sem hangzik rosszul, de ha van az ősosztályban mondjuk 30 tagfüggvény, és ebből le szeretnék tiltani kettőt, akkor 28-at újra deklaráljak? Vagy tegyem priváttá azt a kettőt?

Nem lenne egyszerűbb az ősosztályban azt a kettőt protectedként meghatározni?

Nem, mert ezek kereskedelmi (Qt4) osztályok. Nem én hoztam létre őket.

És a csúnya megoldás nem jó?
Azt a kettőt felülírod a származtatott osztályban olyannal, ami nem csinál semmit.

Hát nem igazán tetszik az ötlet, mert én ebben az osztályban kifejezetten használni szeretném, de a későbbiekben a letiltás javallott. Ha valahol ezeket használni lehetne még, akkor komoly galibát okozhatna. Ha a te javaslatodat alkalmaznám, akkor meg fennállna a veszélye, hogy a későbbiekben használnám vagy más használná, és nem biztos hogy egyből rájönnék/rájönne hol a galiba. Sőt lehet, hogy sokat kellene nyomozni a fals működés kiderítése érdekében.
Így meg a fordító rögtön kidobja, hogy ez privát függvény, ne is akard használni. Szerintem ez korrekt.

Ezért kell private öröklődés. A virtuális tagfüggvényeket nem tudod másképp átdefiniálni. A nem virtuális esetben meg az a baj, hogy nem úgy fog viselkedni, ahogy gondolod.

A fentebbi példámat kiegészítve:

void func(Base *b)
{
  b->Fv(); // Base::Fv() meghívása
}

void func_1(Inherited *b)
{
  b->Fv(); // Inherited::Fv() meghívása
}


...
 Inherited I;

 func(I); // OK, Base::Fv() fut le
 func_1(I); // OK, Inherited::Fv() fut le

Aha. Es miert nem jok neked a Qt4 cuccai? Miert akarod letiltani oket? Mit szeretnel?

Hát én még kezdő vagyok a C++ terén és a Qt-ban még inkább, de megpróbálom elmagyarázni:
Semmi bajom a Qt cuccaival, de itt van ez a QList nevű osztály és én szeretnék egy (My)SortedList osztályt, amit a QList-ből fejlesztenék tovább. Az én osztályom már nem össze-vissza adatokat tartalmazna, hanem bizonyos szempontok szerint rendezett lista lenne, tehát a már meglévő listába való új elem beszúrása csak a rendezési elvnek megfelelően, csak egy bizonyos helyre lenne lehetséges, a bárhová való beszúrás nem megengedett. A mostani QList osztály pedig több olyan tagfüggvénnyel is rendelkezik, amely megengedi a beszúrást bárhová vagy nem oda teszi, ahova az adatot egyébként helyezni kellene. Ezeket szándékoztam letiltani.

Bocs, hogy ilyen sokára válaszolok, de a környékünkön nem volt internet a napokban.

Akkor tegyük tisztába a dolgokat:
A publikus öröklés az "az egy" kapcsolatot fejezi ki.
Azaz mindent megtehetsz a leszármaztatott osztállyal amit az őssel megtehettél. (Ezt próbálod megakadályozni, és ebből adódik az összes problémád amit eddig vázoltál...)

Ez nem igaz a te esetedben, a SortedList az nem egy QList, mert pl nem szúrhatsz be akárhova.

Ami neked kell az a "keresztül implementált" kapcsolat.
Ennek két formája van, a privát öröklés, illetve a tag felvétele.
A privát öröklés akkor kell, ha virtuális fv-eket kell felüldefiniálnod, tehát ez megint nem kell neked.

Összefoglalva, neked ez kell:

template <typename T>
class SortedList<T>
{
...
private:
    QList<T> data;
};

Innentől minden fv-t meg kell írnod, még akkor is, ha az csak meghívja a list fv-ét (pl.: size()). Talán kicsit macerás, de _ez_ az objektumorientált megoldás, a többi gányolás...
Ha a fv-ek tényleg egyszerűek, és inline-ná teszed őket, akkor futásidőben nem fizetsz semmit...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Szia tr3w!

Lehet, hogy úgy helyes elméleti szempontból, ahogy te írod, de engem még mindig nem győztél meg teljesen.
A következőket tudnám ellenvetésnek felhozni:
1, Magát az objektum orientált programozás megalkotását, kitalálását az serkentette többek között, hogy próbáltak olyan rendszert megalkotni, amely a programok továbbfejlesztését egyszerűbbé, átláthatóbbá teszi. Nyílván egy jól kitalált objektum (objektum hierarchia) nagyban megkönnyíti a későbbi módosítást, hisz a módosítás pillanatában már csak egy két új adattagot, tagfüggvényt kell majd hozzáadni a már meglévő osztályhoz. Esetleg egy-két virtuális függvény viselkedésének módosítását kell végrehajtani. Ilyen szempontból a te javaslatod szerintem, eléggé ezen alapelv ellen való mutatvány. Azt javaslod, hogy küszködjem végig azt, amit mások már kiszenvedtek, ahelyett, hogy egy jóval egyszerűbb utat választanék.
Az én személyes véleményem és tapasztalatom, hogy jellemzően az egyszerűbb út a helyes. Minek valamit túlkomplikálni, ha tök egyszerűen megoldhatom máshogy. Ha az objektum orientáltság elve tényleg azt követelné meg, amit te javasolsz, akkor az magát az objektum orientált alapelveket kellene valakinek felülvizsgálat tárgyává tennie, hisz akkor egy bonyolultabb utat kell követnem egy egyszerűbb helyett.
2, Továbbá ezt írod: "a SortedList az nem egy QList". Ezzel a megállapításoddal végképp nem tudok egyetérteni. Természetesen nem 100%-osan az, de először is alapjaiban egy lista, tehát a QList-ben is tárolhatnám ugyanazokat az adatokat, amit a SortedList-ben tárolok majdan. Ha előtte valamilyen külső eszköz szépen a QList-be töltés előtt rendezné a betöltendő adatokat, nem tapasztalhatnánk semmi "fizikai" különbéget a SortedList és a QList között. Ugyanazokat a dolgokat találnánk mindkettőben, ráadásul ugyanolyan sorrendben. Természetesen a SortedList másképpen viselkedik, mint a QList, sőt biztos szükséges 1-2 új adattag (pl. arra vonatkozóan, hogy megengedett-e azonos értékű adatok tárolása a listában vagy sem), és szükség lesz új tagfüggvényekre (pl. valamiféle összehasonlító eljárásra), továbbá módosítani kell bizonyos viselkedéseket (pl. az új cikk beszúrásáét). Ha ezeket figyelembe vesszük, szerintem teljesen egyértelmű, hogy a QList-ből kell származtatni SortedList-et. Nem tudod elképzelni, hogy az öröklés során megörökölt egyes tulajdonságokat, viselkedéseket el kelljen fojtani az új objektumban, hogy eredményesebben tudjon helytállni a neki kiszabott szerepben? Mindenképpen teljesen új, az öröklési hierarchiából kiszakított osztályt kell szerinted készíteni?

Szóval nem győztél meg. Sőt, ahogy leírtam ezeket, és végiggondoltam a kérdést jobban, kifejezetten az én elképzelésemet tartom jobbnak. Ha egy könyvet olvastam volna, és az lett volna benne, hogy ezt így kell csinálni, ahogy te írtad, lehet, hogy most automatikusan úgy csinálnám, és nem gondolkodtam volna el rajta, lehetne-e másképpen.
Most azt gondolom, hogy talán, nem fogom teljesen megérteni az objektum orientált programozás lényegét sohasem, ha a te javaslatod az egyedüli üdvözítő út.

Ha könyvet akarsz olvasni rögtön ajánlok kettőt:
Hatékony C++:
"35. jó tanács: Bizonyosodjunk meg arról, hogy a publikus öröklődés az "azegy" (isa) relációt fejez ki."
"36. jó tanács: Tegyünk külömbséget a felület öröklése és az implementáció öröklése között."

C++ kódolási szabályok:
"37. A nyilvános öröklés felcserélhetőséget jelent.
Az öröklés célja nem az újrahasznosítás, hanem az újrahasznosíthatóság."

Érdemes lenne ezeket elolvasni (illetve a többi jó tanácsot...), okos dolgokat írnak magyarázatokkal.
Kicsit összafoglalom a dolgokat, de nem vagyok grafomán, nem másolom le azt a 10 oldalt amit erről írnak. :)

Tehát az alap probléma:
A nyilvános öröklődés nem arra szolgál, hogy újrahasznosítsuk az ősosztály kódjait. Arra szolgál, hogy az új osztályt mindenhol használhassuk ahol a régit, anélkül, hogy a programot átírnánk, vagy akár csak újrafordítanánk (ha külön fordítási egységben volt).

Ez azt jelenti, hogy minden olyan fv ami az ősosztályt mutatóként vagy referenciaként kapja, használni tudja a leszármazott osztályt is.

Igaz ez a te esetedben? Nem. Azért nem, mert a QList pl. rendelkezik egy insert tagfv-nyel, ami SortedList esetében értelmetlen. Ez a lényeges.

Azt mondod, hogy mindkettő lista, stb. Erre csak a standard példát tudom mondani:
Van egy Rectangle osztályom, és van egy Square. Mivel a négyzet az egy téglalap, a Square a Rectangle-ből öröklődik.
Van a Rectangle osztálynak egy SetWidth fv-e. Normális elvárás, hogy ha a szélességet állítom, a magasság nem változik. Mondjuk van egy fv-em ami szélesíti a téglalapot. Azaz meghívja a setWidth fv-t.
Mi történik, ha meghívom a szélesítést a Square-re? Meghívja a setWidth-fv-t. Ha most módosítom a szélességet, akkor az eredmény nem négyzet. Ha módosítom a magasságot is, akkor megszegem a Rectangle setWidth fv-ének invariánsát.
Tanulság: Az "isa" kapcsolat nem ugyanaz a matematikában, mint a programozásban.

De lemehetünk mélyebb szintre. Azt mondtam, hogy minden olyan fv ami az ősosztályt mutatóként vagy referenciaként kapja, használni tudja a leszármazott osztályt is, anélkül, hogy a programot átírnánk, vagy akár csak újrafordítanánk.

Mit jelent ez? A te esetedben azt, hogy minden fv, ami QList ref-et kap, kaphat SortedListet is, újrafordítás nélkül. Tehát mikor ezt a fv-t írod/írták, a SortedList esetleg még nem is létezett. Az insert tagfv a QListben public, tehát hívható, a fordító kiszámolja a fv pozícióját, kódot generál. Ha te SortedListtel hívod meg, akkor erről a kód mit sem tud, teszi a dolgát, odaugrik az insert fvhez. Láthatóság vizsgálat fordítási időben történik. C++ nem dinamikus nyelv, így nem is történhet máskor...
Ezért nem működhet semmilyen utólagos priváttá tétel.

A QList esetében a probléma az, hogy nem alaposztálynak tervezték, azaz nem arra való, hogy örökléssel specializáld.
Lehetséges volna egy AbstractSequence osztály, insertAnywhere és hasonló fv-ekkel, bár ez így leginkább egy halmaz lesz.
Egyébként QSetből még akár származtathatnád is, csak a te szempontodból semmi értelme, mert úgyse tudnád felhasználni az implementációt.

Egész egyszerűen a QList alapvető tulajdonsága, hogy beszúrhatsz az 5. pozícióba egy elemet, és az ott is marad amíg nem módosítod a listát.

"az öröklési hierarchiából kiszakított osztályt kell szerinted készíteni?"
Ha megnézed a QListet, a QVectort és a QLinkedListet, az öröklési hierarchiában semmi közük egymáshoz. Pedig a te gondolatmeneted alapján nyilván valami közös őstől vagy egymástól kéne származniuk.

De az öröklés célja nem az ősosztály kódjának újrahasznosítása, hanem az új kód újrahasznosíthatósága meglévő kódok által.
Azt mondod, hogy a QList és a SortedList hasonlóak. Igazad van, de ez abban nyilvánul meg, hogy pl mindkettőnek van/lehet random access iterátora, mindkettőnek van push_*,pop_* fv-e, stb. És ezeken keresztül a meglévő algoritmusok használhatják őket (template/generic programming).
Ha úgy tetszik mindkettő hasonló interface-t valósít meg. De a SortedList az nem egy QList.

Talán most már érthető mit akarok mondani...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Szia tr3w!

Hát nagyon köszönöm, hogy ennyit fáradoztál az okításommal! Végül is meggyőztél. A téglalap és a négyzet példája nagyon hatásos volt.

Még csak annyi kérdésem lenne, hogy azért a QObject osztályból származtathatom a SortedList osztályomat ugye? Pl. így jó lesz?

template <typename T>
class SortedList<T> : public QObject
{
    Q_OBJECT
...
private:
    QList<T> data;
};

igen

Egyebkent van olyan OO nyelv ahol at lehet nevezni, vagy el lehet rejteni tagokat, sot olyan is ahol a tagok futas idoben valtozhatnak.

Nyilván dinamikus nyelvekben.

Mondjuk az érdekelne, hogy hogyan biztosított ilyen környezetben, hogy egy ősre írt fv használni tudja a leszármazottat...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Megteheted, csak minek. QList sem ilyen, valószínűleg neked sem kell.
Alapvetően két ok miatt érdemes QObject-ből származtatni:
Signal-Slot, memóriakezelés.
(Ugye QObject tud olyat, hogy ha a parent megszűnik, az felszabadítja a child-eket. Ezért nem nagyon van az átlag Qt GUI kódban delete, miközben new egy rakás...)
Ha ezeket nem használod, akkor nem kell a QObject.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Szia Panther!

Nálam ez a kiegészítés sem fordul le. Mert a func_1() függvényednél kiabál a fordítóm, hogy privát az Fv() függvény. Tehát véletlenül sem hívódik meg Inherited::Fv(). A javaslatod tökéletes. Nálad meghívódik? Milyen fordítót használsz? Az enyém G++.

Egyébként remélem nem kell virtuális függvényt priváttá tennem. Az tényleg nem lenne szerencsés konstelláció.

Sziasztok!

Írtam egy ilyen kódot Phanter kódját követve. Szerettem volna kipróbálni, hogyan működik a priváttá tétel a gyakorlatban virtuális és nem virtuális függvények esetén:


#include <iostream>
class Base{ 
public:
  void f() { std::cout << "\nBase: F\n"; }
  virtual void g() { std::cout << "\nBase: G\n"; } };
class Inherited : public Base {
private:
  void f() {std::cout << "\nNhrtd: F\n";}
  virtual void g() { std::cout << "\nNhrtd: G\n"; } };
void func_fb(Base* o) { o->f(); }
void func_fi(Inherited* o) { o->f(); }
void func_gb(Base* o) { o->g(); }
void func_gi(Inherited* o) { o->g(); }

int main(){
  Inherited I;
  func_fb(&I); 
  func_fi(&I); 
  func_gb(&I); 
  func_gi(&I); 
}

Az már alapból helyes viselkedés volt, hogy a fordító a func_fi() és func_gi() fordításánál fennakadt, mert azokon belül privát függvényekre hivatkoztam.
Ez nagyon helyes ezt akartam.
Viszont, ha ezeket kivettem a programból, akkor már lefordult a program, de futáskor a func_gb() esetében az Inherited::g() futott le. Tehát hiába tettem priváttá, a virtuális jellege miatt mégis csak átadódott számára a vezérlés. Tanulság, hogyha azt akarom, hogy egy virtuális függvény privát is legyen, ugyanakkor ne csináljon hülyeségeket, akkor a leszármazott osztályban egy olyan implementációt kell neki adni, ami mondjuk nem csinál semmit, vagy ha már csinálnia kell valamit, akkor a leszármazott osztályra veszélytelen dolgot kell művelnie.

Ha minden public, ez lesz a kimenet:

Base: F
Nhrtd: F
Nhrtd: G
Nhrtd: G

És igen, ezért nem lehet virtuális fv-ek láthatóságát csökkenteni (csak tudnám, miért nem szól a fordító?).

De igenis lehet virtualis fv-ek lathatosagat csokkenteni, csak nem azt fogja csinalni, amit szeretnel. :-)
Miert szolna ezert a fordito? Ez teljesen legalis kod.

Én is úgy vélem, hogy a fordító helyesen csinálja a dolgát. A programozónak kell gondoskodnia arról, hogy a priváttá tett virtuális függvénye ne csináljon galibát.

Miért nem készítesz egy "burkoló" osztályt? Bár nagyon csúnya, szerintem ez áll a legközelebb az elképzeléseidhez. Az ős osztályt deklaráld privátként.

#include <iostream>

class Base{ 
public:
  void f() { std::cout << "\nBase: F\n"; }
  virtual void g() { std::cout << "\nBase: G\n"; }
private:
  void h() { std::cout << "\nBase: F\n"; }
};

class Inherited : private Base {
private:
  virtual void g() { std::cout << "\nNhrtd: G\n"; }
public:
  void f() {std::cout << "\nNhrtd: F\n";}
  inline void h() { ::Base::h(); }
};

Bár így az Inherited típust nem tudod Base típusúra alakítani, de a metódusokat tetszés szerint tudod variálni (a virtuálisakat is). De ne feledd, amit akarsz az szöges ellentétben van az OO szemlélettel.

Kösz gabaman!

tr3w már meggyőzött a dologról. Lásd fentebb!