Design C ++ - classi vettoriali con diversi tipi di elementi

0

Sto provando a progettare un gruppo di classi correlate. Ad esempio:

  • una classe di tabella contiene un vettore di numero intero e ha una funzione che restituisce numeri interi dal vettore
  • un'altra classe di tabella contiene un vettore di doppio e ha una funzione che restituisce i doppi dal vettore

Idealmente mi piacerebbe che le due classi condividessero lo stesso tipo di puntatore e avessero la stessa interfaccia in modo che possano essere costruite e utilizzate in modo simile dal client. L'unica differenza è che una classe dovrebbe restituire un intero e l'altro doppio.

Ho letto alcuni libri sugli schemi di progettazione e comprendo la differenza tra ereditarietà e composizione. Anche se sono ancora un principiante nel design della classe e sospetto che mi sia sfuggito qualcosa di ovvio. Di seguito sono le mie domande:

  1. È una buona pratica avere membri di dati o membri di funzioni non definiti? Nella seguente codifica di esempio. La classe figlio definirà solo parte dei dati e membro della funzione dalla classe base.
  2. Idealmente mi piacerebbe avere un'interfaccia comune tra le due classi figlio. tuttavia, poiché la funzione get_value1 di una classe restituisce un intero, l'altro get_value2 di un'altra classe restituisce il doppio, devo assegnare loro un nome di funzione diverso. Questo non è fondamentale, ma può essere evitato?
  3. Dovrei evitare l'ereditarietà e creare semplicemente due classi diverse? specialmente quando le due classi non condividono la stessa interfaccia utente?

class Base_Table
{
    std::vector<int> vec_int_;
    std::vector<double> vec_dbl_;
    virtual int get_value1(int);
    virtual double get_value2(int i);
};

class Int_Table : public Base_Table
{
    std::vector<int> vec_int_;
    virtual int get_value1(int i) override
    {
        return vect_int_[i];
    }
 };

class Frac_Table : public Base_Table
{
    std::vector<double> vec_dbl_;
    virtual double get_value2(int i) override
    {
        return vect_int_[i];
    }
};
    
posta DavidY 05.04.2018 - 05:33
fonte

3 risposte

-1

L'ereditarietà non si applica. Potresti farlo con una cosiddetta classe template, ma non la consiglierei. Se T è un tipo di valore come nel tuo caso in genere vuoi eseguire operazioni aritmetiche su di esso e questi non funzionano bene con T. Quindi sarebbe meglio trattare le tabelle integer come diverse dalle tabelle mobili e avere classi diverse, dedicate e indipendenti per loro. Quelle condividono solo i nomi dei metodi per la leggibilità / coerenza.

Modifica : Apparentemente, come sottolineato da Kevin, la particolare limitazione descritta sopra non si applica a C ++ (è valida per C #). Dovresti comunque considerare se tutte le operazioni che vuoi implementare saranno significative per numeri interi e numeri di tipo fluttuanti. Questo potrebbe essere difficile da prevedere. Voglio dire, la soluzione "intelligente" potrebbe morderti nel culo più tardi.

Se ad esempio, se è necessario creare un'operazione che fornisca il valore medio in una colonna, ciò richiederebbe l'applicazione di una logica di arrotondamento se si trattasse di numeri interi. Ma non se tu avessi a che fare con i galleggianti. A quel punto non saresti così felice con la tua fantasia di template.

    
risposta data 05.04.2018 - 07:06
fonte
2

Crea una classe template. Il tuo caso d'uso è il motivo per cui sono stati creati i modelli. Per il tuo esempio questo è abbastanza semplice:

template <class T> 
class Table
{
    std::vector<T> vec;
    T get_value(int i) const { 
        return vec[i];
    }
}

Ma perché stai usando "get_value" invece di definire operator [] come questo:

    T operator[](int i) const {
         return vec[i];
    }
    
risposta data 05.04.2018 - 18:00
fonte
0

C++ design - vector classes with different element types

Sembra che tu voglia avere un tipo di somma o unione taggata . Forse vuoi utilizzare std::variant .

Tieni presente che la maggior parte dei contenitori è omogenea. In particolare, tutti i componenti di un std::vector hanno lo stesso tipo (quindi la tua domanda non è così rigorosa) ha senso). Ma quel tipo potrebbe ovviamente essere un'unione taggata.

Non è chiaro se desideri un vettore di unioni con tag (ad esempio std::vector<std::variant<int,float>> ...) o un'unione di vettori con tag (come std::variant<std::vector<int>,std::vector<float>> ). Questi sono diversi!

Potresti anche avere una tua classe contenente alcuni union (e implementare la tua unione taggata sopra quello). Quindi devi seguire la regola dei cinque .

Potresti anche utilizzare posizionare nuovi e gestire un po 'di memoria da solo.

(ciò che vuoi davvero non è chiaro, ragionare specificando precisamente alcuni tipo di dati astratti , ad esempio la firma della classe desiderata - le sue funzioni di membro pubblico - e il suo comportamento)

Tieni presente che int e double sono tipi POD diversi e in qualche modo incompatibili. Generalmente hanno dimensioni diverse, allineamenti diversi e sono gestiti in modo diverso (ad esempio, siedono in diversi registri di processore ; quindi ABI ha bisogno di sapere su di loro). Quindi il casting tra int e double di solito richiede una conversione (a livello macchina) e non è "libero" (prende almeno un'istruzione macchina e potrebbe perdere informazioni). e una sequenza allineata di byte ha un significato diverso quando viene considerata come una matrice di int e una matrice di double ...

    
risposta data 06.04.2018 - 08:01
fonte

Leggi altre domande sui tag