2009-03-15 18 views
1

Ich schrieb vor kurzem eine Klasse, die B-Spline-Kurven rendert. Diese Kurven sind durch eine Anzahl von Kontrollpunkten definiert. Ursprünglich hatte ich gedacht acht Kontrollpunkte zu verwenden, so habe ich eine Konstante der Klasse, etwa so:Ist es besser, Klassenkonstanten in Datenmembern oder in Methoden zu speichern?

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

Jetzt habe ich diese Klasse erweitern möchten, eine beliebige Menge von Kontrollpunkten zu ermöglichen. Deshalb möchte ich dies ändern:

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

Die Frage ist, ob es nicht besser ist, in Verfahren zum Speichern von Konstanten zu beginnen, zu erleichtern Anpassungsfähigkeit. Mit anderen Worten, es wird nicht besser gestartet somit haben:

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

Der Vorteil davon ist, dass ich konnte nur ein Symbol in dem Verfahren in Frage geändert, statt sich zu bewegen um Konstanten usw.

Ist das eine gute oder eine schlechte?

+0

Ich verstehe ehrlich gesagt die Frage nicht. Was ist anders als die 8 in der Methode zu ändern, um die 8 im Initialisierer der statischen zu ändern? Beide erfordern die gleiche Menge an Arbeit. Die Methode hat den Nachteil, dass es keine reine Kompilierzeitkonstante mehr ist und daher nicht für irgendwas verwendet werden kann. –

Antwort

1

Normalerweise bevorzuge ich die manuelle Wartung so wenig wie möglich.

Die Anzahl der Kontrollpunkte in der Kurve ist gut, die Anzahl der Kontrollpunkte in der Kurve. Es ist keine unabhängige Variable, die beliebig gesetzt werden kann.

So würde ich in der Regel eine konstante Standardcontainer Referenz aussetzen:

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

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

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

Und wenn Sie, wie viele Kontrollpunkte wissen wollen, dann curve.getControlPoints().size() verwenden. Ich würde vermuten, dass in den meisten Anwendungsfällen sowohl die Punkte als auch die Zählung gewünscht sind, und indem ein Standardcontainer verfügbar gemacht wird, können die Iterator-Idiome und die eingebauten Algorithmen der Standardbibliothek verwendet werden eine Funktion wie getControlPointWithIndex in einer Schleife.

Wenn es wirklich nichts anderes in der Kurve Klasse, ich sogar so weit gehen könnte:

typedef std::vector<Point> Curve; 

(oft eine Kurve nicht macht selbst, als ein Renderer Klasse Details über den Rendering hast Pipeline, eine Kurve als rein geometrischer Artefakt)

+0

Große Antwort :) Sie machen einen sehr guten Punkt bezüglich der Minimierung der Anzahl von Abhängigkeiten. Die Punkte sind jedoch in meiner eigenen benutzerdefinierten Matrix-Klasse gespeichert. Haben Sie nicht das Gefühl, dass das Offenlegen dieser Ebene von Implementierungsdetails die Kopplung zwischen Client und Klasse potenziell erhöht? – fluffels

1

Um Ihre Frage besser zu beantworten, sollte man auch wissen, wie die controlPointCount Variable gesetzt ist. Ist es außerhalb deiner Klasse aufgestellt? In diesem Fall sollten Sie auch einen Setter definieren. Oder die Curve-Klasse ist allein verantwortlich für die Einstellung? Wird nur zur Kompilierzeit oder auch zur Laufzeit gesetzt.

Wie auch immer, eine magische Zahl auch in dieser Form vermeiden:

int getControlPointCount() {return 8;} 

Das ist besser:

int getControlPointCount() {return CONTROL_POINT_COUNT;} 

Verfahren haben den Vorteil, dass Sie die interne Implementierung ändern können (mit einem konstanten Wert , lesen Sie aus einer Konfigurationsdatei, ändern Sie den Wert dynamisch), ohne das Äußere der Klasse zu beeinflussen.

+0

Warum konnten Sie CONTROL_POINT_COUNT nicht auch als Methode definieren ?! –

+0

Warum sollte es hier vorteilhaft sein, eine magische Zahl zu vermeiden? – fluffels

+0

@Earwicker: Wenn es eine Methode ist, dann ist es besser, eine Signatur einer Methode zu haben. @fluffels: Magische Zahlen machen Code weniger lesbar und weniger wartbar. Wenn Sie an einer Stelle den Wert der Konstante ändern müssen, ist es einfacher, wenn es eine Definition dafür gibt. – kgiannakakis

2
int getControlPointCount() {return _controlPointCount;} 

Dies ist ein Accessor. Ein Const-Static für einen Accessor zu tauschen, ist nicht wirklich ein Gewinn, wie litb darauf hingewiesen hat. Was Sie wirklich benötigen zukunftssichere ist wahrscheinlich ein Paar von Accessor und Mutator.

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

ich auch in einem Design-const für die Accessor werfen würde und machen es:

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

und die entsprechende:

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

nun der große Unterschied mit einem statischen Objekt ist, dass die Anzahl der Kontrollpunkte nicht mehr ein Attribut auf Klassenebene, sondern ein Exemplar auf Instanzebene ist. Dies ist eine Designänderung. Willst du es so?

Nit: Ihr statischer Zählerstand auf Klassenebene ist public und benötigt daher keinen Accessor.

0

Im Allgemeinen sollten alle Ihre Daten privat sein und über Getter und Setter aufgerufen werden. Andernfalls verletzen Sie die Kapselung. Wenn Sie nämlich die zugrunde liegenden Daten verfügbar machen, sperren Sie sich selbst und Ihre Klasse in eine bestimmte Darstellung dieser zugrunde liegenden Daten.

In diesem speziellen Fall, dass ich das so etwas wie das folgende getan hätte ich denke:

class Curve 
{ 

    protected: 

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

    private: 

     static int _controlPointCount = 0; 
}; 
+0

Ich mag es nicht, die statischen und nicht statischen Teile zu mischen. Wenn Sie nach einem statischen Attribut suchen, initialisieren Sie es in der Deklaration der Variablen und nicht in jeder Konstruktion des Objekts. Beide Setter und Getter sollten statisch sein. –

+0

(Forts.) Ihre obige Implementierung setzt die Variable immer auf 0 zurück, wenn eine neue Instanz erstellt wird, wobei vorherige Sätze verworfen werden. –

0
class Curve 
{ 
    private: 
     int _controlPointCount; 

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

    public:  
     curve() 
     { 
      _controlPointCount = 8; 
     } 

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

ich einen Code wie diesen zu schaffen, mit Set-Funktion in privat, so dass kein Körper spielen mit der Anzahl der Kontrollpunkte, bis wir zur nächsten Phase der Entwicklung übergehen ... wo wir aktualisieren, um die Kontrollpunktanzahl zur Laufzeit zu aktualisieren. Zu diesem Zeitpunkt können wir diese set-Methode vom privaten in den öffentlichen Bereich verschieben.

0

verlassen Während die Frage zu verstehen, habe ich eine Reihe von konzeptionellen Problemen mit dem Beispiel:

  • Was fürist der Rückgabewertwenn die Anzahl der Kontrollpunkte nicht begrenzt ist?
    • Ist es MAXINT?
    • Ist es die aktuelle Anzahl der Kontrollpunkte auf der Kurve (also die Logik zu brechen, der sagt, dass dies die größtmögliche Anzahl von Punkten?)
  • Was passiert, wenn Sie tatsächlich eine Kurve mit MAXINT zu schaffen versuchen, Punkte? Ihnen wird irgendwann der Speicher ausgehen.

Die Schnittstelle selbst scheint mir problematisch. Wie bei anderen Standardauflistungsklassen sollte die Klasse die Begrenzung der Anzahl der Punkte gekapselt haben, und die AddControlPoint() sollte einen Fehler zurückgegeben haben, wenn eine Beschränkung der Größe, des Arbeitsspeichers oder eines anderen Verstoßes aufgetreten ist.

Wie für die spezifische Antwort, stimme ich mit kiannakakis überein: eine Mitgliedsfunktion ermöglicht mehr Flexibilität.

0

Ich neige dazu, Konfiguration + Konstante (Standardwert) für alle "stabile" Werte durch die Ausführung des Programms zu verwenden. Mit einfachen Konstanten für Werte, die nicht geändert werden können (360 Grad -> 2 Pi Radianten, 60 Sekunden -> 1 Minute) oder deren Änderung den laufenden Code unterbrechen würde (Minimal-/Maximalwerte für Algorithmen, die sie instabil machen).

Sie haben es mit verschiedenen Designproblemen zu tun. Zuerst müssen Sie wissen, ob die Anzahl der Kontrollpunkte ein Klassen- oder Instanzenwert ist. Dann, ob es eine Konstante auf einem der beiden Ebenen ist.

Wenn alle Kurven die gleiche Anzahl von Kontrollpunkten in Ihrer Anwendung haben müssen, dann handelt es sich um einen statischen Wert auf Klassenebene. Wenn verschiedene Kurven eine unterschiedliche Anzahl von Kontrollpunkten haben können, handelt es sich nicht um einen Wert auf Klassenebene, sondern um einen Instanzwert.

In diesem Fall, wenn die Anzahl der Kontrollpunkte während der gesamten Lebensdauer der Kurve konstant ist, handelt es sich um eine Instance-Level-Konstante. Wenn sie sich ändern kann, ist sie auch nicht konstant auf dieser Ebene.

// 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()); 
} 
0

für Integral-Typ bin ich mit usualy:

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

Wenn Konstante muss nicht für alle Einheiten außer Klassenimplementierung ich Konstanten in * CPP-Datei deklarieren.

namespace 
{ 
const int CONTROL_POINT_COUNT = 8; 
} 
0

Konstanten im Allgemeinen sollten nicht innerhalb von Methoden definiert werden. Das von Ihnen ausgewählte Beispiel hat zwei einzigartige Funktionen. Erstens, es ist ein Getter; Zweitens ist der zurückgegebene Typ ein int. Aber die Definition von Konstanten besteht darin, sie mehr als einmal zu verwenden und sich auf sie bequem beziehen zu können. Wenn Sie "8" im Gegensatz zu "controlPointCount" eingeben, sparen Sie möglicherweise Zeit und möglicherweise keine Wartungskosten. Dies ist jedoch normalerweise nicht der Fall, wenn Sie immer Konstanten innerhalb von Methoden definieren.