Il dilemma di implementare l'ereditarietà virtuale

2

Sto lavorando su un linguaggio di programmazione, e sono giunto al dilemma se supportare o meno l'ereditarietà virtuale.

Come designer e implementatore del linguaggio, inclusa quella funzionalità rappresenta una maggiore complessità.

Come sviluppatore di software indipendente dal linguaggio, sembra che l'unico vantaggio derivante dall'inclearlo sia quello di facilitare e consentire la progettazione di software scadente.

I casi in cui ho riscontrato l'utilizzo dell'ereditarietà virtuale sembrano tutti candidati migliori per le interfacce "add-on".

Naturalmente, riconosco che lavorare con il codice di terze parti esistente su cui non si ha il controllo è sicuramente qualcosa che potrebbe richiedere il supporto per l'ereditarietà virtuale.

Ma per me questo non è il caso, ho una lavagna chiara e piena libertà di definire i paradigmi e gli idiomi del linguaggio di programmazione. Pertanto, la soluzione più elegante ed efficiente sembra essere quella di eliminare semplicemente la possibilità che possa sorgere la necessità dell'ereditarietà virtuale e omettere del tutto la funzionalità.

Ho anche notato che è una caratteristica piuttosto di nicchia, delle varie lingue che ho studiato, il C ++ è l'unico a supportarlo. Portandomi a presumere che non è tutto ciò che è essenziale.

Naturalmente, potrei anche trascurare qualcosa, che mi ha spinto a sondare la comunità degli sviluppatori per l'input sull'argomento.

EDIT: per chiarire come richiesto, le prestazioni e l'efficienza della memoria sono alcuni degli obiettivi principali della lingua. Il virtualismo, il dinamismo e qualsiasi funzionalità di programmazione di alto livello sono facoltativi e inclusi solo se necessario. Non è una lingua in cui tutto è riferimento e chiamate virtuali.

Invece di

class WingedAnimal : extend Animal {}

il mio piano attuale è di evitare la necessità di affrontare la duplicazione dei membri per mezzo di

class WingedAnimal : require Animal {}

che essenzialmente consente a WingedAnimal di utilizzare Animal senza ereditarlo, garantendo che tutti gli utenti di WingedAnimal siano compatibili.

    
posta dtech 11.07.2018 - 13:47
fonte

2 risposte

2

L'ereditarietà virtuale utilizzata da alcune implementazioni C ++ ha senso solo in presenza di vincoli abbastanza specifici:

  • Le classi hanno un layout di oggetto fisso che è noto in fase di compilazione.
  • Un'istanza può essere trasmessa a un tipo di classe base. Pertanto, l'ereditarietà deve incorporare il layout della classe base nel layout della sottoclasse.
  • La lingua consente l'ereditarietà multipla.
  • Per evitare duplicati di strutture con ereditarietà multipla, la posizione del layout della classe base non è fissa, ma è disponibile tramite il puntatore indiretto aggiuntivo.

A causa di questa indiretta, l'ereditarietà virtuale implica necessariamente un sovraccarico di tempo inevitabile in fase di esecuzione. O la tua lingua dovrà pagare questo overhead per tutte le classi, o fare una distinzione tra classi normali e classi sicure da MI. MI con classi normali è sicuro fino a quando non portano a più embeddings, ma questo ad es. significa che ereditare da un'altra classe base non è più un cambiamento compatibile con le versioni precedenti.

L'approccio alternativo è che i layout di classe non sono corretti, ma che le classi abilitate a MI devono solo accedere ai campi di istanza tramite metodi / proprietà virtuali. Il layout e queste proprietà vengono quindi forniti dalla classe più derivata che viene effettivamente istanziata. Questo è ad es. l'approccio utilizzato da C #, in cui le interfacce non possono dichiarare campi ma possono dichiarare proprietà (virtuali). Questo ha un impatto sulle prestazioni minore di quello che si potrebbe pensare grazie alla compilazione JIT con inlining estremamente intelligente. Approcci simili sono utilizzati da alcune implementazioni dei tratti, poiché i tratti non possono dichiarare campi ma possono richiedere e fornire metodi.

La maggior parte dei sistemi MI semplicemente non presuppone un layout di oggetti fisso ma risolve metodi e campi per nome . Per esempio. questo è fondamentale per l'approccio MI di Python. Tuttavia, sembra che questo potrebbe essere indesiderato per la tua lingua.

Qualsiasi di queste soluzioni implica un sovraccarico. L'ereditarietà virtuale implica forse il minimo sovraccarico, ma ha un strong impatto sull'ergonomia linguistica: le classi devono scegliere esplicitamente tra prestazioni e funzionalità MI. Il modo più semplice per risolvere questo dilemma è di fare una scelta di progettazione linguistica fondamentale. O decidere di proibire l'implementazione-MI, o decidere di abbandonare le prestazioni di C / C ++ e l'ethos "non pagare per ciò che non si usa". Prenderò seriamente in considerazione la seconda opzione perché ha funzionato bene nella pratica: la maggior parte dei programmi semplicemente non ha bisogno dell'ultimo bit di prestazioni disponibili.

Le implementazioni MI e OOP in generale pongono problemi più difficili del semplice layout degli oggetti (a cui l'ereditarietà virtuale è una possibile soluzione). Inoltre, devi pensare alle strategie di invio del metodo (ad esempio confrontare C ++ - come multipli vtables per oggetto, Java / C # -stile singolo vtable + ricerca tabella di interfaccia, puntatori grassi stile Go / Rust dove l'oggetto non ha un membro vtable e la distribuzione basata sulla tabella hash come in Python). Considerare anche il problema dell'ordine di inizializzazione in una classe MI. Per esempio. in Python tutti i costruttori in una gerarchia MI devono avere la stessa firma che è estremamente indesiderabile.

Per essere assolutamente chiaro, questa risposta presuppone che in effetti si desideri supportare la distribuzione dinamica. L'ereditarietà quindi non include solo l'implementazione della classe base come un mixin, ma consente anche l'invio dinamico attraverso l'interfaccia della classe base. Le difficoltà dell'IM riguardano in gran parte il mantenimento di un'interfaccia compatibile di classe base. Se vi sbarazzate di ciò in modo che tutte le chiamate di metodo e gli accessi al campo passino attraverso la sottoclasse staticamente conosciuta, allora non avete bisogno di un tipico apparato ereditario come vtables o eredità virtuale - siete liberi di calcolare un layout arbitrario per ogni classe.

    
risposta data 14.07.2018 - 14:02
fonte
0

In molti modi, l'ereditarietà dell'implementazione di qualsiasi tipo è fuori moda in questi giorni. L'ethos comune oggi è preferire l'aggregazione sull'ereditarietà. Questo suggerisce che una soluzione ai problemi di come implementare l'ereditarietà è di evitare completamente il problema: consente solo l'ereditarietà interfaccia (es. Sottotipizzazione). Fornire invece strumenti utili per semplificare l'aggregazione: la delega dichiarativa di metodi non implementati di un'interfaccia a un particolare membro, ad esempio, potrebbe essere un modo diverso per ottenere gli stessi risultati che potrebbero essere più chiari e più comprensibili.

    
risposta data 21.07.2018 - 03:12
fonte