'Assegnare' una classe base a una classe derivata?

3

Sto esaminando alcuni codici legacy e mi sono imbattuto in un caso in cui lo sviluppatore ha "esteso" una classe esistente con membri aggiuntivi, utilizzando l'ereditarietà come sua arma preferita. Essenzialmente si riduce a qualcosa del genere:

struct A
{
    int something_old;
};

struct B : public A
{
    int something_new;
};

C'è una sezione di codice in cui hanno un'istanza di A, ma in realtà vogliono un'istanza di B. Ecco come sono riusciti a ottenerla:

auto aa = GetAn_A(); // for example
   :
B bb;
*(A*)&bb = aa; // <-- code smell?
bb.something_new = 42;

Questo sembra funzionare bene: taglia bb e poi assegna alla parte affettata. Ma non posso fare a meno di pensare che in questi giorni illuminati del C ++, tutto ciò che l'hacking 'n' slicing con & 's e *' è un po 'vecchia scuola.

C'è un odore di codice qui e, se sì, quanto è grande un odore? Dovrebbe essere possibile scrivere così?

Se è smelly, dove l'autore ha iniziato a perdere la trama e cosa avrebbe dovuto / avrebbe dovuto fare per ottenere il risultato desiderato, cioè un'estensione a una classe esistente (tenendo conto di mente che A e B sono significativamente più complicati di questo in realtà, e che A è stato scritto qualche tempo prima di B)?

    
posta WalderFrey 24.11.2017 - 16:06
fonte

2 risposte

7

Tutto dipende da cosa A e B sono e significano.

Poiché i sottooggetti di classe base possono fare cose che i sottooggetti membri non possono (essendo vuoto non disturbando il layout del tipo, esponendo automaticamente le loro funzioni membro come proprietari, ecc.), ci sono momenti in cui l'ereditarietà da una classe ha senso anche senza polimorfismo .

Nel caso precedente, nessuno dei due tipi è polimorfico, quindi l'ereditarietà viene utilizzata per scopi polimorfici non dinamici. Cioè, l'intento dell'utente non è di trattare B come se fosse un A .

Essenzialmente, l'utente sta trattando la classe base di B come un membro subobject. Esiste una funzione che restituisce un valore di tipo A e desideri archiviarlo in un oggetto di tipo B . Il fatto che sia una classe base piuttosto che un membro è essenzialmente un dettaglio di implementazione.

Si noti che C ++ 17 normalizza questa interpretazione in misura maggiore. Nel tuo esempio, B è un aggregato in C ++ 17 e può utilizzare l'inizializzazione di aggregazione per inizializzare tutti i suoi sottooggetti:

B bb = {aa, 42};

Questo tipo di cose può essere abbastanza utile. La possibilità di avere i metodi della classe base come membri della tua classe è qualcosa che non puoi fare facilmente se ne fai un membro. Ad esempio, se stai creando una classe string_view -like con alcuni membri aggiuntivi, sarebbe stupido scrivere dozzine di funzioni di inoltro quando si eredita da esso quando si può semplicemente derivare da esso. Mentre l'ereditarietà pubblica è un po 'esagerata, non tutti vogliono scrivere un sacco di dichiarazioni di using .

L'ereditarietà, anche l'ereditarietà pubblica, non sempre significa "è un".

Ora, continuerei a fallire durante la revisione del codice a causa del modo in cui viene gestito il compito. Dovrebbe essere solo (A&)bb = aa; .

    
risposta data 24.11.2017 - 17:16
fonte
0

Oltre alla risposta eccellente di @NicolBolas; possiamo fare quella sequenza usando costruttori e senza casting.

...
A aa = GetA();
...
B bb(aa);    // instead of: B bb; *(A*)&bb = aa;
...

Per questo, A e B hanno bisogno di costruttori appropriati (e si noti che anche quei costruttori non richiedono lanci scomodi).

    
risposta data 24.11.2017 - 17:28
fonte

Leggi altre domande sui tag