Requisiti per la progettazione di una gerarchia in C ++ per l'estensione di tipi C interi. Problema di progettazione dell'interfaccia

3

Sto provando a progettare classi di gerarchia che estendono i tipi C interi. Quello che sto cercando di ottenere è fondamentalmente il seguente:

Ho una classe base chiamata Integer questa classe è una classe astratta, l'unica cosa che vorrei fornire è fondamentalmente le operazioni di base tra interi, ovvero +,-,*,/,&,^,|,~,etc if I'm missing something , solo l'interfaccia nessuna implementazione. Quindi eseguirò una sottoclasse di tale classe con StaticInteger e DynamicInteger la differenza è che StaticInteger utilizza i modelli per descrivere la dimensione del tipo in fase di compilazione, mentre DynamicInteger la dimensione può essere modificata in fase di esecuzione.

Alcuni casi d'uso sarebbero i seguenti per entrambe le classi:

StaticInteger<14> is1 = 3;
StaticInteger<32> is2 = 15;
StaticInteger<16> is3 = is1 + is2;

DynamicInteger id1;
id1.setWidth(14);
id1.setWidth(16);
id1 = is1;

Internamente i dati sono rappresentati usando gli array, quindi per esempio il ridimensionamento nel tipo dinamico verrebbe ottenuto usando new/delete operatori;

Fondamentalmente ciò che mi lascia perplesso è come progettare correttamente l'interfaccia della classe astratta base Integer. Diciamo che progetterei direttamente la classe StaticInteger che dovrei provare a fare qualcosa del tipo:

template <int L>
class StaticInteger {
public:
  //bla bla
  template <int M>
  StaticInteger<L> operator+(StaticInteger<M> x);
private:
  //bla bla
};

La dinamica sarebbe invece

class DynamicInteger {
 public:
   DynamicInteger operator+(DynamicInteger x);
 private:
};

ma come dovrei specificare l'interfaccia che coprirebbe ogni possibile interfaccia di tale operatore? Tra statico e dinamico penso sia chiaro che in generale per un dato operatore l'implementazione dell'algoritmo potrebbe essere diversa.

Solo un'ulteriore nota, capisco che c'è probabilmente un problema filosofico dietro ciò che sto cercando di fare, dal momento che un operatore è in realtà una funzione in C ++, e per essere definito correttamente richiede tutti i tipi specificati a priori, in la classe base.

Aggiornamento: solo un design alternativo, stavo pensando che forse avrei potuto astrarre il concetto di operazioni usando una classe separata (come un modello di strategia).

    
posta user8469759 03.10.2016 - 14:47
fonte

1 risposta

3

Ci sarebbe un modo semplice per uscire da questo, che potrebbe evitare di definire tutti i tipi di overload per operator+ in DynamicInteger : basta definire un costruttore basato su StaticInteger , e lasciare che il compilatore cerchi di organizzare implicitamente conversioni:

class DynamicInteger {
 public:
    template <int M>
    DynamicInteger (StaticInteger<M> m ) {
           cout<<"construct Dynamic from "<< M<<endl;  // for demo purpose
    } 
    DynamicInteger operator+(DynamicInteger x) { ... }
 ...
};

Quindi puoi scrivere quanto segue:

StaticInteger<16> a = 10, b=20; 
DynamicInteger x = a;         // coonvert a StaticInteger value to DynamicInteger
DynamicInteger z = x + b;     // mix StaticIntger in expressions to get them converted

Demo

Ulteriori considerazioni "filosofiche" (vedi la discussione nei commenti)

Non hai bisogno di una classe base comune né di una gerarchia. È sufficiente una corretta definizione degli operatori, inclusa la conversione ed eventualmente gli operatori che mescolano i tipi.

Questo non ti impedisce di usare una classe base comune se pensi che possa essere d'aiuto. Ad esempio, potresti decidere di implementare prima un DynamicInteger e quindi lasciare ereditare StaticInteger da esso (ad esempio, definendo le dimensioni in fase di costruzione e aumentando un overflow anziché aumentare dinamicamente le dimensioni). D'altra parte, un tipo di template puro avrebbe il vantaggio di prestazioni più elevate grazie all'ottimizzazione del tempo di compilazione e una gestione più leggera della memoria con array di dimensioni fisse (templatizzati).

Filosoficamente parlando, non hai nemmeno bisogno di una gerarchia: devi solo definire le regole. Ok, puoi aggiungere, moltiplicare, dividere numeri interi, frazionari, reali e persino complessi e mescolarli a seme; hanno in comune che sono tutti numeri. Ma d'altra parte puoi anche aggiungere, moltiplicare, dividere i vettori, che non sono numeri ma un concetto matematico astratto. Puoi anche dividere una torta di mele con un numero, nonostante il fatto che non abbiano un antenato comune.

Infine, diamo un'occhiata a list<Integers> . Innanzitutto, il C ++ standard non fornisce questo tipo di universalità per i tipi di base: dovresti scegliere un tipo specifico al momento della compilazione. Se non vuoi prendere una decisione, puoi rimandarla all'esecuzione, il che significa creare un oggetto più complesso, come un'unione o una variante. Per la tua lista di interi generalizzati, la situazione è la stessa. Quindi hai davvero bisogno di questo tipo di generalizzazione? Se sì, potresti effettivamente usare una classe base comune ma con i vantaggi e gli inconvenienti che ho già menzionato.

    
risposta data 03.10.2016 - 23:03
fonte

Leggi altre domande sui tag