Newsgroup: it.comp.lang.c++
Titolo: ereditarieta' pubblica e privata

Salve a tutti,
ho una classe Derivata che eredita' pubblicamente da una classe Base.
class Base
{
// ...
};
class Derivata :public Base
{
// ...
};
Adesso vorrei aggiungere delle funzionalita' alla classe Derivata, che trovo
in una certa classe Log.
Diciamo che potrei incapsulare un oggetto di tipo Log nella classe Derivata,
per poter usare le funzionalita' della classe Log.
class Log
{
public :
    Print();
}
class Derivata : public Base
{
public :
    Log m_log;
}
Ed ho letto che in questi casi l'incapsulazione e' meglio
dell'ereditarieta'.
ma se volessi ereditare da Log solo l'implementazione facendo quindi
un'ereditarieta' privata quale sarebbe la sintassi, trattandosi di
un'ereditarieta' mista multipla (private, public)?
Maurus

 ha scritto nel messaggio
news:aff7qk$e231p$1...@ID-125555.news.dfncis.de...


 un'ereditarieta' mista multipla (private, public)?
class Derivata : public Base, Log
{
};
Ciao,
Nicola

 ha scritto nel messaggio
news:aff7qk$e231p$1...@ID-125555.news.dfncis.de...

Non so se dico una stupidata, sono alle primissime armi!
Class Derivata: public Base, private Log
Spero che sia quel che volevi!
Ciao
Francesco

 Nicola
La soluzione piu' ovvia era quella giusta!
Grazie Nicola, volevo anche chiederti se in questi casi tu preferisci fare
cosi' (ereditarieta' multipla mista), quando hai gia' una classe che eredita
pubblicamente.
Oppure preferisci inglobare l'oggetto di tipo Log nella classe derivata?
Maurus


E' esatto anche questo grazie.
Anche escludendo la key private come ha scritto Nicola, e' giusto poiche' in
C++ per default il contenuto di una classe e la modalita' di ereditarita' e'
privato.
Tu preferisci ereditare in modo privato o inglobare l'oggetto di tipo Log
nella classe Derivata?
Maurus

 Oppure preferisci inglobare l'oggetto di tipo Log nella classe derivata?
A priori *aggrego* un'istanza di Log all'interno della classe ed
eventualmente le delego direttamente i metodi che mi servono, ad es.
class B {
  public:
    int f();
};
class A {
  public:
    int f() { return b->f(); }
  private:
    B * b;
};
Ciao,
Nicola

 Log nella classe Derivata?
in genere se e` possibile si preferisce usare il contenimento, dato che 
e` una relazione meno forte che non l'ereditarieta`. Sutter ne parla 
ampiamente nell'item 24 di "Exceptional C++"; ti cito una Linea Guida 
tratta da tale item:
"Preferite l'aggregazione (anche nota come "composizione", 
"stratificazione", "delegazione") all'ereditarieta`. Quando si realizza 
una relazione E`-IMPLEMENTATO-IN-TERMINI-DI preferite sempre l'uso 
dell'aggregazione e non l'ereditarieta`"
ciao Anto
-- 
Antonio Cuni, 20 anni, studente
Brainbench C++ Master: 4.73, higher than 99% of all previous test takers.
C Language: 3.79, higher than 92% of all previous test takers.
Guida C/C++ di Programmazione.it: 
http://cpp.programmazione.it

 dell'aggregazione e non l'ereditarieta`"
Non ho letto il libro di Sutter, ma se questo è un consiglio che non riporta
una spiegazione non serve a molto. Tutti userebbero sempre il contenimento
(c'è una certa differenza comunque tra composizione, aggregazione,
stratificazione e delegazione, ed anche se Sutter sbaglia non mi stupisce,
Booch confonde Incapsulazione e Information Hiding :-). Un consiglio
motivato può trovarsi nel "C++ Manuale di stile" di C. Pescio, che anche se
un po' datato per quel che riguarda la discussione di alcuni costrutti,
risulta impareggiabile per questioni di stile e legate all'OOP in generale.
Il consiglio è: utilizzare l'ereditarietà privata quando la relazione è del
tipo Is-A, ma per ragioni implementative. Un esempio potrebbe essere un
FixedSizeStack, derivato pubblicamente da Stack per averne l'interfaccia, e
privatamente da una classe Array per utilizzarne l'implementazione. A ben
pensarci un FixedSizeStack è anche un array, ma per ragioni implementative,
di qui l'uso dell'ereditarietà privata al posto del contenimento. Per una
discussione più approfondita si leggano i capitoli 10 e 14.
Ciao,
--
*---------------------------------------
* Fulvio Esposito
* E-Mail: 
ful...@libero.it
* WWW: 
http://digilander.iol.it/fesposito
*---------------------------------------

 ragioni implementative. 
Questo e` un altro paio di maniche, noi stavamo parlando di modellare 
relazioni HAS-A; in effetti l'unico altro possibile utilizzo 
dell'ereditarieta` privata oltre al contenimento e` proprio questo: 
poter utilizzare l'LSP limitatamente ad alcune porzioni di codice.
Anzi, proprio quest'ultimo e` un motivo in piu` per preferire il 
contenimento, se possibile: se utilizziamo l'ereditarieta` privata per 
modellare un HAS-A c'e` il rischio che i metodi o i friend utilizzino 
erroneamente la classe come un IS-A. Con il contenimento questo rischio 
non si presenta.


 --
Uno dei motivi (psicologici o di pigrizia), che mi fanno optare per
l'ereditarieta' privata piuttosto che per il contenimento e' il fatto che
con il contenimento ho un livello di indirezione in piu', mentre con
l'ereditarieta' privata, posso accedere direttamente al membro della classe
base.
Maurus

 non si presenta.
Non riesco a capire che intendi qui. LSP sta per Liskov Substitution
Principle? non vedo che c'entra con l'ereditarietą privata, visto che questa
non puņ essere assolutamente intesa come sub-typing.
Spiegati meglio.
Fulvio


 base.
Questo non è affatto vero. Se il contenimento è diretto, ereditarietà
privata e contenimento sono praticamente la stessa cosa (in termini di
performance)
Fulvio


 della classe base.
il livello di indirezione in piu` e` solamente sintattico: come ha gia` 
detto anche Fulvio dal punto di vista delle performance non cambia 
assolutamente nulla.
Cmq la possibilita` di accedere direttamente ai membri della classe base 
e` un'arma a doppio taglio: puo` rendere piu` veloce la scrittura del 
codice ma al contempo puo` anche rendere piu` complicata la 
manutenzione. Pensa ad un caso come questo, in cui modelliamo due 
relazioni HAS-A con l'ereditarieta` multipla privata:
class Pippo: private Pluto, private Topolino {
public:
        void foo()
        {
                eat_something();
                speak();
                go_away();
        }
};
Quando scrivi la classe puo` sembrare tutto chiaro, pero` se il codice 
deve essere revisionato da qualcun'altro (o da te stesso dopo qualche 
tempo) possono esserci dei malintesi: chi, fra Pluto e Topolino, 
mangiera` qualcosa? Chi parlera? Chi andra` via?
Cmq questo e` un problema secondario: basta andarsi a vedere 
l'interfaccia di Pluto e Topolino e la cosa si risolve. Il vero problema 
e` un altro: supponiamo che la classe Pluto abbia il metodo 
eat_something() e quella Topolino non l'abbia: il codice qua sopra 
funziona correttamente.
Ora supponiamo che in un secondo tempo il programmatore di Topolino 
decida di implementare un metodo eat_something(): il codice qua sopra, 
che prima era corretto, improvvisamente non compila piu`; una modifica 
apparentemente innocua quale l'aggiunta di un metodo all'interfaccia 
pubblica (che, in teoria, non dovrebbe avere alcuna ripercussione sugli 
utenti della classe) ha improvvisamente rotto del codice esistente.
Per cautelarsi da questi problemi IMHO e` meglio esplicitare gli scope 
dai quali prendere i metodi:
                Pluto::eat_something();
                Topolino::speak();
                Topolino::go_away();
In questo modo non ci sono ambiguita` e il codice e` senz'altro piu` 
chiaro.
Pensandoci bene la possibilita` che ci siano problemi di questo tipo mi 
spinge ancora di piu` a preferire il contenimento, nei casi in cui e` 
sufficiente.


 questa non puņ essere assolutamente intesa come sub-typing.
si, con LSP intendo Liskov Substitution Principle?
Per spiegarmi meglio penso che la cosa migliore sia un esempio:
class Pippo { ... };
void foo(Pippo*); // sfrutta il polimorfismo
class Topolino: private Pippo {
        void bar()
        {
                foo(this); // all'interno dei membri Topolino E`-UN Pippo
        }
};
In questo caso l'ereditarieta` privata ci rende possibile utilizzare 
l'LSP in un contesto ridotto (ovvero solo i membri e i friend).


 l'LSP in un contesto ridotto (ovvero solo i membri e i friend).
Bisognerebbe fare qualche precisazione su come l'ingegneria del software
intende i "principi". L'ingegneria del software studia modi e tecniche per
la gestione della complessità del software, nonchè per un suo sviluppo
ingegneristico. I principi sono regole da seguire per ottenere tutti quei
vantaggi di cui si parla tanto: qualità, reliability, testability,
reusability, extension, etc. LSP non significa *poter* usare foo(this) come
nell'esempio, anzi l'esempio che proponi è proprio una violazione a tale
principio, perchè usa Topolino in un contesto dove non andrebbe usato (al
posto di Pippo, che è una base privata e quindi non è un super-tipo).
Ciao,


 base privata e quindi non è un super-tipo). Ciao,
in sintesi LSP significa: "dovunque puoi usare la classe base puoi usare 
anche quella derivata".
Un LSP ristretto significa: "in alcuni punto dove puoi usare la classe 
base puoi usare anche quella derivata".
Non ci vedo nessuna contraddezione ne`, tantomeno, nessuna "violazione di 
principio". E, soprattutto: chi hai mai detto che una classe base 
privata non e`, in generale, un super-tipo? Lo standard C++ dice proprio 
il contrario: una classe base privata non e` un super-tipo, *ad 
eccezione* di quando e` usata all'interno di metodi e friend.


 eccezione* di quando e` usata all'interno di metodi e friend.
Questo puň portare a codice del tipo:
#include <iostream>
class Pippo {
 public:
     virtual void foo2() {
   std::cout << "Pippo";
  }
};
void foo(Pippo* p) // sfrutta il polimorfismo
{
 p->foo2();
}
class Topolino: private Pippo {
 public:
        void bar()
        {
                foo(this); // all'interno dei membri Topolino E`-UN Pippo
        }
};
int main()
  {
  Topolino* top = new Topolino;
  top->bar();
  }
Questa č una piccola estensione del tuo esempio. Facendo partire il
programma incredibilmente viene stampato Pippo. Dico incredibilmente perchč
la funzione foo2 č diventata privata di Topolino e non dovrebbe essere
accessibile, quindi si dovrebbe avere un errore in compilazione.  Se
modifico foo all'interno di Topolino e per motivi di implementazione non
rispetto le pre/postcondizioni di foo2 di Pippo, ottengo un cambiamento nel
comportamento di foo, e questo equivale a violare LSP.
Fulvio


Stavo parlando di sintassi e non di performance.
E' legato alla pigrizia del programmatore che deve scrivere delle cose in
piu'.
Maurus

 comportamento di foo, e questo equivale a violare LSP.
Non trovo questo esempio calzante, perche' il problema e' indipendente
dal tipo di eredita'.
Ciao,
Nicola

 dal tipo di eredita'.
Il fatto è che foo, che è una funzione non membro richiede un puntatore a
Pippo, aspettandosi quindi qualcosa che abbia l'intefaccia di Pippo.
Topolino non ha questo requisito. Sono i meccanismi del linguaggio che ti
permettono di avere ciò (la conversione a Pippo* del this e la chiamata di
una funzione di Pippo su tale sotto-oggetto dall'interno di Topolino).
Fulvio


 una funzione di Pippo su tale sotto-oggetto dall'interno di Topolino).
E cara grazia che te lo permettono.
Scusami, sai, ma i tuoi discorsi danno proprio l'impressione che per te
l'OOP sia "buona a priori", indipendentemente dalla verifica concreta
della sua applicabilita' e della sua efficacia a risolvere problemi
specifici. In questo modo ti chiudi ad un ampio spazio di soluzioni
possibili che potrebbero calzare meglio coi problemi che affronti.
Reitero l'invito a leggere "Multiparadigm design for C++" di Jim
Coplien.
Ciao,
Nicola

 cambiamento nel comportamento di foo, e questo equivale a violare LSP.
ancora non capisco: come faccio a "modificare foo all'interno di 
Topolino"? foo e` una funzione globale!


 Coplien.
Forse non avrò personalmente molta esperienza per poterti dire che l'OO è
realmente molto buona. Ma quando mi metto davanti al mio CASE tool i
template sono l'ultima cosa che mi passa per la testa. Ho scelto di imparare
l'OO, ammetto probabilmente sull'onda del suo successo, ma devo ammettere
che mi ci trovo benissimo, e le soluzioni che ho trovato sono sempre state
ottime. I template li uso poco, preferisco quando possibile non dover
ricompilare, e questo i template non me lo consentono. Ritengo che una
soluzione ad un problema non scaturisca dal paradigma che adotto, bensì
dalla mia intelligenza. La soluzione non è ad oggetti, funzionale o
generica, è un idea. La sua realizzazione può esserlo. Se una soluzione è
fortemente dipendente da un paradigma, probabimente è perchè il nostro modo
di pensare è stato plasmato da quel paradigma. L'informatica è nata per
risolvere problemi reali, le soluzioni devono essere reali, non funzionali
ad oggetti o che altro.
Fulvio

 ad oggetti o che altro.
Scusami ma qui contraddici tutto quello che hai detto finora. La
soluzione ad un problema *e'* a oggetti, o funzionale, o generica, o una
combinazione di questi ed altri paradigmi. Il punto e' che non sara' mai
il paradigma a garantire la qualita' della soluzione: con l'OO si
possono fare pastrocchi tanto quanto con la programmazione procedurale,
ed anche peggiori. Tra l'altro esprimi il mio pensiero quasi
esattamente: sospetto che le tue "ottime" soluzioni OO siano proprio
dovute al fatto che il tuo modo di pensare e' troppo influenzato dalla
tuo innamoramento per il paradigma dell'orientamento ad oggetti.
Probabilmente faresti bene ad abbandonare il C++ per lo Smalltalk, che
mi pare piu' adatto ad applicare in modo "estremo" il tuo paradigma
preferito. Per contro usare il C++ solo ad oggetti sarebbe una rinuncia
a molte delle sue potenzialita'.
Ciao,
Nicola

 Nicola
Ciao,
Fulvio

 l'OOP. 
anche qui secondo me ragioni troppo a comparti stagni: prima di tutto 
quello imperativo e` un "metaparadigma"; un linguaggio imperativo e` 
quello che ti consente di impartire ordini alla macchina: personalmente 
non conosco (e non so neppure se possa esistere) un linguaggio OO che 
non sia imperativo. Cmq, immagino che con "imperativo" tu volessi 
intedere il paradigma "proceduarale", tipico ad esempio del C o del 
Pascal.
Anche il tuo giudizio sulla programmazione generica (nel seguito GP, 
Generic Programming) mi lascia perplesso: dalla tua frase sembra che 
consideri OOP e GP come due paradigmi ortogonali e assolutamente 
diversi. Questo puo` anche essere vero in un linguaggio a tipizzazione 
statica come il C++, ma non e` vero in generale. Pensa ad esempio a 
Python: il codice scritto in Python e` intrinsecamente "generico" (nel 
senso C++-ista del termine): l'approccio del Python e` OO e generico 
alle stesso tempo.
Per finire, volevo riportare la citazione che Stroustrup ha messo in 
testa al capitolo "Progetto e sviluppo" di TCPL:
        "Non esiste alcuna panacea" (F. Brooks)


 ciao Anto
Ciao,
Fulvio

 complessi. Vuol dire che stai già sviluppando.
prendi un mazzo di carte:
a) ordinalo per seme in ordine alfabetico crescente e, per ciascun seme, 
ordina le carte per numero in ordine crescente
b) ora ordinalo per seme decrescente e per numero crescente
c) ora per seme crescente e numero decrescente
d) ora per seme e numero decrescenti
e) ora ordinalo prima per numero crescente e seme crescente
f, g, h) ecc. ecc.
ora ordina i libri della tua libreria in ordine alfabetico
oppure per autore
oppure per anno di pubblicazione
o per dimensioni
o per quanto spesso li consulti
ecc.
prova un po' a modellare ad oggetti questi esempi qua, poi prova a 
modellarli con i template...


 dettagli di implementazione.
Quello che chiami dettagli di implementazione sono a mio avviso la parte
piu' importante del nostro lavoro ed e' importante distinguere in quale
parte derivano direttamente da un modello del dominio ed in quale parte
sono esclusivamente pertinenti allo spazio della soluzione. In entrambe
i casi, se lo strumento prescelto e' il C++ i template sono uno
strumento imbattibile per rappresentare elementi di comunanza che
restano immutabili nel corso dell'esecuzione del programma.
Ciao,
Nicola

 restano immutabili nel corso dell'esecuzione del programma.
Immutabili č una parola grossa!
Ciao,
Fulvio

 mondo.
Questo non e' il pattern Observer, che prevede l'esistenza di alcuni
oggetti con determinati ruoli. Questo si chiama "Callback" ed e' un
"idiom" comune a molti linguaggi e che nasce effettivamente in ambito
procedurale. Observer e' un'estensione concettuale del meccanismo di
callback. Tra parentesi i pattern della Gang of Four sono
*intrinsecamente* orientati agli oggetti (leggi il sottotitolo del libro
se non ci credi).
Ciao,
Nicola

 piu'.
Perché siccome un programmatore davanti alla tastiera è una specie di
dattilografo, non può scrivere una decina di caratteri in più...comprendo
umanamente la pigrizia ma è immotivata, specie quando qualcuno(non su questo
ng) scrive che commentare il codice e dare nomi leggibili alle variabili
costa il quintuplo del tempo...Che schifo(non tu, chi ha detto questo).Ciao!
--
Paolo Giarrusso
Per rispondere togliere solo "TOGLIMI" dall'indirizzo, non _spam.

 ricompilare, e questo i template non me lo consentono.
Solo per ora; nello standard esiste export template, che consente di
inserire i template nel file .cpp e non nel .h. Certo, ci vorrà almeno un
anno( e non certo col visual c++ né col borland, sono occupati a pensare ai
Web Services e .NET; guardiamo a g++). Ciao
