2009-02-19 17 views
8

Sto imparando C++ (e programmazione in generale) e sto provando a fare sia una classe Point che una classe Line.Classe Point e Line in C++?

Una riga deve essere composta da 2 oggetti punto.

I guru del linguaggio C++ possono esaminare il mio lavoro e dirmi se questo è il modo in cui dovresti usare in modo appropriato puntatori, riferimenti e classi?

class Point 
{ 
    private: 
     int x, y; 
    public: 
     Point() : x(0), y(0) {} 
     Point(int x, int y) : x(x), y(y) {} 
} 

class Line 
{ 
    private: 
     Point *p1; 
     Point *p2; 
    public: 
     Line(Point &p1, Point &p2) : p1(p1), p2(p2) {} 

     void setPoints(Point &p1, Point &p2) 
     { 
      this->p1 = p1; 
      this->p2 = p2; 
     } 
} 
+0

L'esempio non deve essere compilato, perché si stanno mescolando i puntatori ("Punto * p1") con riferimenti ("Punto e p1"). – ChrisW

risposta

7

Non si dovrebbero utilizzare i puntatori nel codice. Usa oggetti reali. I puntatori vengono effettivamente usati molto raramente in C++.

class Point 
{ 
    private: 
     int x, y; 
    public: 
     Point() : x(0), y(0) {} 
     Point(int x, int y) : x(x), y(y) {} 
} 

class Line 
{ 
    private: 
     Point p1; 
     Point p2; 
    public: 
     Line(const Point & p1, const Point & p2) : p1(p1), p2(p2) {} 

     void setPoints(const Point & ap1, const Point & ap2) 
     { 
      p1 = ap1; 
      p2 = ap2; 
     } 
} 
+0

I puntatori non vengono usati raramente in C++. Ovviamente non hai fatto alcuna programmazione GUI, dove sono coinvolti gli alberi. =] – strager

+0

doh, credo che in oltre 20 anni di programmazione in C++ non mi sono mai imbattuto in un albero - sciocco me. –

+0

Non ho bisogno di usare i puntatori per costruire un albero. –

2

io preferirei questo ...

class Point 
{ 
    private: 
     int x, y; 
    public: 
     Point() : x(0), y(0) {} 
     Point(int x, int y) : x(x), y(y) {} 
} 

class Line 
{ 
    private: 
     Point p1; 
     Point p2; 
    public: 
     Line(const Point &p1, const Point &p2) : p1(p1), p2(p2) {} 

     void setPoints(const Point &p1, const Point &p2) 
     { 
      this->p1 = p1; 
      this->p2 = p2; 
     } 
} 
+0

p1 = p1; Sicuramente qualche errore qui? –

+0

Sì, hai ragione. Ora risolto. :) –

2

Non v'è alcuna necessità di utilizzare i puntatori nella classe linea.

Inoltre, la seguente riga non è corretto:

Line(Point &p1, Point &p2) : p1(p1), p2(p2) {} 

Perché? Stai assegnando un Point & (p1) a Point * (Linea :: p1), che è illegale. Vorresti dei puntatori lì.

La classe Point non ha modo di modificare i suoi dati. Non troppo utile ...

Una classe linea per me sarebbe simile a questa:

class Line 
{ 
    private: 
     Point p1, p2; 

    public: 
     Line() 
     { 
     } 

     Line(Point start, Point end) : 
      p1(start), p2(end) 
     { 
     } 

     const Point &startPoint() const 
     { 
      return p1; 
     } 

     Point &startPoint() 
     { 
      return p1; 
     } 

     const Point &endPoint() const 
     { 
      return p2; 
     } 

     Point &endPoint() 
     { 
      return p2; 
     } 
}; 

È ora possibile modificare la riga come questa:

Line line; 
line.startPoint() = Point(4, 2); 
line.endPoint() = Point(6, 9); 
+0

non sei un credente nell'uso di const allora? –

+0

scusa, ignora l'ultimo - ha interpretato erroneamente il codice –

+0

Perché hai 2 metodi di acquisizione per ogni punto, uno con const e uno senza? –

1

Un paio di cose che ho notato :

  • È possibile combinare entrambi i costruttori di punti in un singolo costruttore con valori predefiniti.
  • L'utilizzo dei puntatori non è necessario. Usa l'oggetto stesso.
  • Stai utilizzando sia i puntatori che i riferimenti in modo intercambiabile. Non mescolarli o vedere l'ultimo punto.
+0

Un singolo costruttore con valori predefiniti consentirebbe anche a Punto (1) di costruire un punto (con lo stesso effetto di Punto (1,0)). Preferisco i due costruttori. –

1

Vedo un piccolo valore nel fare i puntatori di Point (tranne che per il valore di ironia). Your Point prende 8 byte su un sistema a 32 bit (2 int). Un puntatore impiega 4 byte. stai salvando 4 byte.

Per quanto riguarda la correttezza, il costruttore della linea prende i riferimenti, ma li stai assegnando ai puntatori. Questo non dovrebbe nemmeno essere compilato. Stai anche facendo la stessa cosa in setPoints. Sarebbe meglio semplicemente rendere i due punti oggetti reali e copiare i loro valori.

+0

Eh? Come fa il puntatore a salvare 4 byte: punta agli 8 byte di un'istanza Point, il che significa che * aggiunge * 4 byte? Questo è IMHO un po 'fuorviante. – mghie

+0

Mi riferivo alla classe stessa. Supponiamo che tu abbia 10 classi di linea che utilizzano tutte la stessa istanza Point. È un risparmio minimo. –

+0

Ma questo è ancora peggio, come sapresti quando liberare un Punto * se viene referenziato da più di una Linea? Tutto questo cercando di trattare C++ come linguaggio GC non porta da nessuna parte. Posso solo essere d'accordo con zabzonk: nel moderno C++ raramente c'è un motivo per usare i puntatori grezzi. – mghie

0
class Line 
{ 
    private: 
     Point *p1; /* No memory allocated */ 
     Point *p2; /* No memory allocated */ 
    public: 
     Line(Point &p1, Point &p2) : p1(p1), p2(p2) {} 

     void setPoints(Point &p1, Point &p2) /* passed references to Point objects */ 
     { 
      this->p1 = p1; /* asssiging Point objects to Point *! */ 
      this->p2 = p2; /* asssiging Point objects to Point *! */ 
     } 
} 

La funzione setPoints() non funzionava - a prima vista. Dovrebbe essere

  void setPoints(Point &p1, Point &p2) 
      { 
       this->p1 = &p1; /* asssiging Point objects to Point *! */ 
       this->p2 = &p2; /* asssiging Point objects to Point *! */ 
      } 

Poi di nuovo, non abbiamo controllo su quando P1 e P2 vengono distrutte.È meglio creare questo-> p1 e questo-> p2 usando i dati in p1 e p2 in modo che il distruttore abbia il controllo sulla memoria

5

+1 quello che dice zabzonk.

il momento di utilizzare i puntatori, come lei li hanno utilizzati, potrebbe essere:

  1. Hai diversi punti
  2. Si desidera creare linee usando quei punti
  3. Si desidera modificare i valori di i punti e le linee sono cambiate implicitamente.

Il passaggio 3 sopra può essere raggiunto se le linee contengono puntatori a punti esistenti. Tuttavia introduce complessità (ad esempio, "quando si distrugge un'istanza Punto, cosa succede alle istanze di Linea che contengono puntatori al Punto?"), Che non esiste quando (come suggerito da zabzonk) ogni Linea contiene i propri valori Punto.

5

Indipendentemente dal fatto che i membri del punto della classe di linea siano o meno puntatori o non crea un tipo di classe molto diverso. L'uso dei puntatori si tradurrà in un approccio classico stile CoGo, che può essere considerato come punti come chiodi in una tavola e linee che sono elastici che connettono quelle unghie. Cambiare un punto è come muovere un chiodo, tutte le linee associate funzionano automaticamente, il che è auspicabile in alcune applicazioni.

L'utilizzo di punti letterali indica che le linee sono tutte indipendenti l'una dall'altra, il che è appropriato per altri tipi di applicazioni.

Questa è una decisione di progettazione critica per le classi in questa fase.

Modifica: Come indicato in altri post e il commento di seguito, l'utilizzo di semplici puntatori per ottenere l'associazione tra più linee e punti presenta anche un grave problema potenziale. In particolare, se un punto viene cancellato o spostato in memoria, tutti i puntatori che fanno riferimento a quel punto devono essere aggiornati. In pratica, questo tende a essere superato usando ID punto univoci per cercare un punto piuttosto che semplici puntatori. Ciò consente anche alle strutture CoGo di essere facilmente serializzate/salvate. Quindi la nostra classe di punti avrebbe un membro statico per ottenere un punto basato su ID e la nostra classe di linea avrebbe due ID punto anziché puntatori.

+0

Ho svalutato la tua risposta oggi, ma ora non ne sono più tanto sicuro: c'è molta confusione in altre risposte su quando utilizzare i puntatori e, mentre sono d'accordo con la tua premessa, non riesci a risolvere i problemi di gestione a vita dell'uso di Point * completamente. Forse potresti modificare la tua risposta altrimenti eccellente? – mghie

0

Il compilatore darà errori su questo codice:

  1. Nella lista di inizializzazione del costruttore linea, si sta assegnando p1 riferimento Punto a p1 membro della classe, che è un puntatore a Point. Per ottenere questa compilazione, utilizzare Line (Punto & p1, Punto & p2): p1 (& p1), p2 (& p2).
  2. Lo stesso problema si verifica nel metodo setpoint. Che shoudl essere modificati per this-> p1 = & p1, ecc
  3. problema minore: dopo la chiusura} della classe, messo in un punto e virgola (;)

penso che conclude la sintassi.

C'è un altro problema con il codice:

I membri della classe P1 e P2 di linea sono puntatori a punto le istanze. Chi crea queste istanze e chi le cancellerà quando non saranno più necessarie?Se si progetta la classe come è ora, il codice che crea un'istanza Line passa due istanze Point al costruttore. Se queste istanze Point vengono eliminate prima che la Linea sia, la Linea viene lasciata con due puntatori penzolanti (puntatori che non si riferiscono più a un'istanza valida).

Inoltre, le due istanze Point che ora fanno parte della Linea possono essere modificate dal codice al di fuori della classe Line. Ciò può portare a situazioni molto indesiderabili.

In questa situazione vorrei dichiarare i membri p1 e p2 come Punto (anziché un puntatore su Punto). In questo modo, le istanze Point passate al costruttore vengono copiate nei membri della classe. Finché esiste la Linea, i membri del Punto esisteranno. Possono essere modificati solo dalla classe di linea stessa.

0

Prima di chiedere l'opinione del guru della lingua, inizia a pensare al design, in questo caso soprattutto alla vita dei tuoi oggetti: un punto deve esistere senza una linea? Le linee condividono punti? Chi crea un punto, quando smette di esistere? Due punti con le stesse coordinate sono identici o uguali (uno potrebbe essere rosso, l'altro potrebbe essere blu)? ...

Sembra che la maggior parte di noi sia d'accordo sul fatto che si dovrebbe usare la semantica del valore su una base di codice così piccola. A volte, il problema richiede che lo stesso oggetto (cioè Punto) sia referenziato da più lati, quindi usa puntatori (o riferimenti).

La scelta tra un puntatore e un riferimento è ancora qualcos'altro. Preferisco utilizzare un riferimento quando implemento l'aggregazione (una riga non può esistere senza i suoi endpoint) e un puntatore quando implementa una relazione meno "intima".

+0

La mia descrizione della differenza tra un riferimento e un puntatore è su http://stackoverflow.com/questions/448056/c-singleton-getinstance-return/448068#448068 – ChrisW

+0

tx. Molto discussione anche su quell'argomento. – xtofl

0

Usa gli oggetti punto nella classe Line. La proprietà dei punti non è chiara e rischi di finire con puntatori penzolanti o perdite di memoria.

Il costruttore predefinito è un problema poiché raramente si desidera creare un Punto su (0,0). È meglio impostare i valori di default x, y su qualcosa come MAXINT e affermare che qualsiasi utilizzo di Point non ha MAXINT come uno dei suoi valori. Avere una funzione is_valid() in modo che i client possano testare. La tua classe Line può anche presupporre che i suoi due punti non siano non validi. Il pay off di non permettere ad un punto valido di avere un valore MAXINT è che puoi rilevare quando i Punti non sono stati inizializzati correttamente, e zapping fuori alcuni bug altrimenti difficili da trovare nell'uso della classe.