c ++ coding practice class vs. funzioni "gratuite"

3

Attualmente sto scrivendo il mio primo progetto più grande in c ++. La sua algebra lineare di base. Sono consapevole che esistono librerie di boost e altre, ma per vari motivi ho bisogno di scrivere le mie. Scopo della libreria è eseguire determinate operazioni vettoriali (aggiunta, sottrazione, ecc ...) per l'analisi statistica dei dati. Quindi è necessario prendere in considerazione la facilità d'uso delle funzioni.

Ho iniziato a scrivere una classe:

typedef std::vector<double> vec;
typedef std::vector<std::vector<double> > mat;

class Vector{
public: 
    Vector(){} 
    Vector(vec){}
    ~Vector(){}

    //functions to act on the vectors
    vec add(double){...}
    //many more... 

private:
    vec v
}; 

Presto mi resi conto che per l'aritmetica vettoriale semplice la classe aveva alcuni inconvenienti: gli operatori di assegnazione mancanti venivano a perseguitarmi piuttosto rapidamente.

Poiché non ho esperienze di codifica a lungo termine, voglio sapere se un file "semplice" che contiene tutte le funzioni necessarie come:

 from Vector.h:

 typedef std::vector<double> vec;
 typedef std::vector<std::vector<double> > mat;

 vec add(vec v, double d){...};
 ...

è una soluzione adeguata per questo tipo di problema o se è preferibile considerare la soluzione di classe?

    
posta Vincent 14.09.2014 - 09:43
fonte

4 risposte

12

Quando vuoi rappresentare concetti distinti, devi creare tipi separati. Sì, questo può venire con qualche piastra di riscaldamento per il sovraccarico dell'operatore, ma avere tipi distinti può offrire vantaggi significativi lungo la linea.

std::vector è un array dinamico generico, non un vettore matematico (nonostante abbia preso in prestito il suo nome). Quindi sì, dovresti creare un tipo separato.

Se le operazioni che vuoi vengano poi implementate come membri o come funzioni libere è una decisione progettuale separata. Raccomando di leggere Effective C ++.

    
risposta data 14.09.2014 - 12:36
fonte
2

La soluzione migliore sarebbe quella di sovraccaricare l'operatore.

vec operator+(const vec& rhs) const

è ciò che useresti per una dichiarazione, questa pagina la descrive in modo abbastanza dettagliato: link

Quindi potresti semplicemente dire:

vec a;
vec b;
vec c;

// Do stuff to initialize a and b

c = a + b;
    
risposta data 14.09.2014 - 09:39
fonte
1

Un vantaggio di una funzione gratuita nel tuo caso particolare è che puoi implementare sia l'aggiunta di vettori che i doppi

vec add(vec v, double d);

e vice versa

vec add(double d, vec v);

Questo non è possibile con le funzioni membro.

Le funzioni membro, tuttavia, hanno il vantaggio di poter accedere e persino modificare gli stati interni degli oggetti, il che può essere utile (generalmente, questa è la la ragione per scrivere i functors invece delle funzioni). Ma suppongo che nel tuo caso questo non sia né richiesto né nemmeno necessario.

Per quanto riguarda il fatto di scrivere una classe wrapper o meno, è una domanda separata. Dipende dal fatto che il vettore debba avere uno stato aggiuntivo o funzioni membro che si desidera chiamare con la sintassi della funzione membro (ad esempio v.sort() ). A volte è solo più elegante scrivere sort(v) .

Ci sono anche un paio di altri problemi con il tuo codice. Innanzitutto, non vedo alcun motivo per far passare i vettori in giro per valori. Utilizzare invece i riferimenti const. In secondo luogo, l'algebra vettoriale è il classico esempio di dove usa l'overloading dell'operatore suggerisce se stesso.

Oh, e non dimenticare di scrivere l'operatore + = (è comodo e può essere implementato in modo più efficiente).

    
risposta data 14.09.2014 - 09:49
fonte
1

Il concetto alla base di std::vector è non quello di un vettore matematico, ma quello di una matrice di dimensioni dinamiche.

Aggiungendo un'operazione aritmetica ad esso, rende quelle operazioni disponibili a qualsiasi codice nel tuo sviluppo può usare std :: vector per fare altro che le operazioni di algebra lineare.

Per questo motivo, se hai bisogno di un vettore matematico, è meglio definire un nuovo tipo indipendente.

Ovviamente niente può impedirti di usare std::vector al suo interno per contenere i valori.

Se le operazioni devono essere membri o la funzione libera applicata a quel tipo ... è più una questione di gusti che altro: a.add(b) e add(a,b) hanno esattamente la stessa potenza semantica. Personalmente preferisco il secondo, poiché mette sia a che b allo stesso livello, o anche operator+(a,b) (in modo che tu possa scrivere a+b )

Quando implementi la matrice, tuttavia, pensa un po 'prima di implementarli come vector of vectors per vari motivi:

  • se i vettori sono implementati un "dinamicamente considerevole" un vetore di vettori avrà tutte le righe indipendentemente considerevoli e memorizzate a vicenda separatamente con tutta la struttura (di solito tre puntatori) per seguirle. Per le matrici piccole, i vettori (come lo spazio 3d-proiettivo di 4 componenti) sono più sovradimensionati dei dati.
  • Un vettore dinamico di vettori dinamici privilegia l'accesso per righe (o per colonne), ma una matrice deve essere in grado di invertire facilmente il proprio ruolo (cosa al prodotto Cij = Aik*Bkj matrix)
  • Le matrici sono esse stesse "spazi vettoriali" contro + - e scalare * e /.

È molto meglio pensare alle matrici come tipi indipendenti, implementato come un vettore semplice, accessibile esternamente con due indici, ma le cui operazioni di base sono implementate in termini di vettore interno.

Probabilmente arriverò a qualcosa di simile a questo:

template<class T>
class vect
{
    std::vector<T> m;
public:    
    explcit vect(size_t n) :m(n) {}
    size_t size() const { return n; }
    T& operator[] (size_t i) { return m(i); }
    const T& operator[] (size_t i) const { return m[i]; }
};

template<class T>
vect<T> operator+(const vect<T>& a, const vect<T>& b)
{ 
    vect<T> z(a.size()); 
    for(size_t i=0; i<a.size(); ++i) 
        z[i] = a[i]+b[i]; 
    return z; 
}


template<class T>
class matrix
{
    vect<T> m;
    size_t R;
public:
    matrix(size_t r, size_t c) :m(r*c), R(r) {}
    matrix(const vect<T>& s, size_t r) :m(s), R(r) {}

    const vect<T>& as_vect() const { return m; }
    size_t rows() const { return R; }
    size_t cols() const { return m.size()/R; }

    T& operator()(size_t r, size_t c) { return m[r*cols()+c]; }
    const T& operator()(size_t r, size_t c) const { return m[r*cols()+c]; }
};


template<class T>
matrix<T> operator+(const matrix<T>& a, const matrix<T>& b)
{ return matrix<T>(a.as_vect()+b.as_vect(),a.rows()); }

Lo stesso può essere fatto sia per matrix che per vect su operator- , e unary + e - , nonché per operator*(vect<T>, T) e operator*(matrix<T>, T) (prodotto per scalare) e le controparti commutative operator*(T, vect<T>) e operator*(T, matrix<T>) .

Deve quindi essere implementato operator* int le varianti matrix*matrix , matrix*vect , vect*matrix .

Esistono altre tecniche più sofisticate per evitare la ripetizione di loop e le variabili locali all'interno delle funzioni (come la costruzione tramite lambda e i proxy vettoriali che formano le espressioni template da utilizzare per ottenere sezioni vettoriali come viste trasposte matrice e così via. .. ma potrebbe essere troppo lontano per quello che stai facendo)

    
risposta data 14.09.2014 - 21:53
fonte

Leggi altre domande sui tag