Domanda

Di recente ho scritto una classe che rende le curve B-spline. Queste curve sono definite da un numero di punti di controllo. Inizialmente, avevo intenzione di usare otto punti di controllo, quindi ho aggiunto una costante alla classe, in questo modo:

class Curve
{
   public:
      static const int CONTROL_POINT_COUNT = 8;
};

Ora voglio estendere questa classe per consentire una quantità arbitraria di punti di controllo. Quindi voglio cambiare questo in:

class Curve
{
   public:
      int getControlPointCount() {return _controlPointCount;}
};

La domanda è se non è meglio archiviare le costanti nei metodi, per facilitare l'adattabilità. In altre parole, non è meglio aver iniziato così:

class Curve
{
   public:
      int getControlPointCount() {return 8;}
};

Il vantaggio di questo è che avrei potuto cambiare un solo simbolo nel metodo in questione, invece di spostarmi tra le costanti ecc.

È una buona pratica o una cattiva?

È stato utile?

Soluzione

In genere preferisco mantenere il minor numero possibile di giunti manualmente.

Il numero di punti di controllo nella curva è, beh, il numero di punti di controllo nella curva. Non è una variabile indipendente che può essere impostata a piacimento.

Quindi di solito espongo un riferimento standard const const:

class Curve
{   
    private:
        std::vector<Point>& _controlPoints;

    public:      
        Curve ( const std::vector<Point>& controlPoints) : _controlPoints(controlPoints)
        {
        }

        const std::vector<Point>& getControlPoints ()
        {
            return _controlPoints;
        }
};

E se vuoi sapere quanti punti di controllo, usa curve.getControlPoints (). size () . Sospetto che nella maggior parte dei casi d'uso vorresti comunque i punti e il conteggio, ed esponendo un contenitore standard puoi usare i modi di dire dell'iteratore della libreria standard e gli algoritmi integrati, piuttosto ottenere il conteggio e chiamare una funzione come getControlPointWithIndex in un ciclo.

Se davvero non c'è nient'altro nella classe curva, potrei anche arrivare fino a:

typedef std::vector<Point> Curve;

(spesso una curva non si riproduce da sola, poiché una classe di rendering può avere dettagli sulla pipeline di rendering, lasciando una curva come puramente artefatto geometrico)

Altri suggerimenti

int getControlPointCount() {return _controlPointCount;}

Questo è un accessorio. Sostituire una const static con un accessor non è in realtà un guadagno come ha sottolineato litb. Ciò di cui hai veramente bisogno per a prova di futuro è probabilmente una coppia di accessori e mutatori.

int getControlPointCount() {return _controlPointCount;} // accessor

Vorrei anche inserire una const-design per l'accessor e renderlo:

int getControlPointCount() const {return _controlPointCount;} // accessor

e il corrispondente:

void setControlPointCount(int cpc) { _controlPointCount = cpc;} //mutator

Ora, la grande differenza con un oggetto statico è che il conteggio dei punti di controllo non è più un attributo a livello di classe ma uno a livello di istanza. Questa è una modifica del design . Lo vuoi in questo modo?

Nit: Il conteggio statico a livello di classe è public e quindi non ha bisogno di un accessor.

Per rispondere meglio alla tua domanda, dovresti anche sapere come è impostata la variabile controlPointCount. È impostato fuori dalla tua classe? In questo caso, dovresti anche definire un setter. O la classe Curve è l'unica responsabile per l'impostazione? È impostato solo in fase di compilazione o anche in fase di esecuzione.

In ogni caso, evita un numero magico anche in questa forma:

int getControlPointCount() {return 8;}

Questo è meglio:

int getControlPointCount() {return CONTROL_POINT_COUNT;}

Un metodo ha il vantaggio di poter modificare l'implementazione interna (utilizzare un valore costante, leggere da un file di configurazione, modificare il valore in modo dinamico), senza influire sull'esterno della classe.

class Curve
{   
    private:
        int _controlPointCount;

        void setControlPointCount(int cpc_arg)
        {
            _controlPointCount = cpc_arg;
        }

    public:      
        curve()
        {
            _controlPointCount = 8;
        }

        int getControlPointCount() const
        {
            return _controlPointCount;
        }
};

Creerò un codice come questo, con la funzione set in privato, in modo che nessun corpo possa giocare con il conteggio dei punti di controllo, fino a quando non passiamo alla fase successiva di sviluppo ... dove aggiorniamo iniziamo ad aggiornare il conteggio dei punti di controllo su runtime. in quel momento, possiamo spostare questo metodo impostato da privato a pubblico.

Mentre capisco la domanda, ho un certo numero di problemi concettuali con l'esempio:

  • Qual è il valore di ritorno per getControlPointCount () quando il numero di punti di controllo non è limitato?
    • È MAXINT?
    • È l'attuale numero di punti di controllo sulla curva (infrangendo così la logica che dice che questo è il maggior numero possibile di punti?)
  • Cosa succede quando si tenta effettivamente di creare una curva con punti MAXINT? Alla fine rimarrai senza memoria.

L'interfaccia stessa mi sembra problematica. Come altre classi di raccolta standard, la classe avrebbe dovuto incapsulare la sua limitazione sul numero di punti e il suo AddControlPoint () avrebbe dovuto restituire un errore se si fosse verificata una limitazione di dimensioni, memoria o qualsiasi altra violazione.

Per quanto riguarda la risposta specifica, concordo con kgiannakakis: una funzione membro consente una maggiore flessibilità.

Tendo a usare la configurazione + costante (valore predefinito) per tutti i valori 'stabili' attraverso l'esecuzione del programma. Con costanti semplici per valori che non possono cambiare (360 gradi - > 2 pi radianti, 60 secondi - > 1 minuto) o la cui modifica spezzerebbe il codice corrente (valori minimo / massimo per algoritmi che li rendono instabili).

Stai affrontando diversi problemi di progettazione. Innanzitutto devi sapere se il numero di punti di controllo è un valore di classe o di istanza. Quindi se si tratta di una costante in uno dei due livelli.

Se tutte le curve devono condividere lo stesso numero di punti di controllo nell'applicazione, si tratta di un valore di livello (statico). Se curve diverse possono avere un numero diverso di punti di controllo, non si tratta di un valore a livello di classe, ma piuttosto di uno a livello di istanza.

In questo caso, se il numero di punti di controllo sarà costante per tutta la vita della curva, allora è una costante a livello di istanza, se può cambiare, non è costante nemmeno a questo livello.

// Assuming that different curves can have different 
// number of control points, but that the value cannot 
// change dynamically for a curve.
class Curve
{
public:
   explicit Curve( int control_points )
      : control_points_( control_points )
   {}
   // ...
private:
   const int control_points_;
};

namespace constant
{
   const int spline_control_points = 8;
}
class Config
{
public:
   Config();
   void readFile( std::string const & file );

   // returns the configured value for SplineControlPoints or
   // constant::spline_control_points if the option does not 
   // appear in config.
   int getSplineControlPoints() const;
};

int main()
{
   Config config;
   config.readFile( "~/.configuration" ); // read config

   Curve c( config.getSplineControlPoints() );
}

Per il tipo integrale sto usando di solito:

class Curve
{
   public:
      enum 
      {
          CONTROL_POINT_COUNT = 8
      };
};

Se la costante non è necessaria per nessuna entità tranne l'implementazione della classe, dichiaro le costanti nel file * .cpp.

namespace
{
const int CONTROL_POINT_COUNT = 8;
}

In generale, tutti i tuoi dati dovrebbero essere privati ??e accessibili tramite getter e setter. Altrimenti si viola l'incapsulamento. Vale a dire, se esponi i dati sottostanti, blocchi te stesso e la tua classe in una particolare rappresentazione di tali dati sottostanti.

In questo caso specifico avrei fatto qualcosa di simile al seguente credo:

class Curve
{

   protected:

      int getControlPointCount() {return _controlPointCount;}
      int setControlPointCount(int c) { _controlPointCount = c; }

   private:

      static int _controlPointCount = 0;
};

Le costanti in generale non devono essere definite all'interno dei metodi. L'esempio che stai scegliendo ha due caratteristiche uniche. Innanzitutto, è un getter; secondo, il tipo restituito è un int. Ma il punto di definire le costanti è usarle più di una volta e poterle fare riferimento in modo conveniente. Digitando "8" al contrario di "controlPointCount" può farti risparmiare tempo e potrebbe non sembrare sostenere un costo di manutenzione, ma questo non sarà in genere vero se definisci sempre costanti all'interno dei metodi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top