Frage

Ist es eine gute Praxis, einen Klassenkonstruktor zu haben, der Standardparameter verwendet, oder sollte ich separate überlastete Konstrukteure verwenden? Zum Beispiel:

// Use this...
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo(const std::string& name = "", const unsigned int age = 0) :
        name_(name),
        age_(age)
    {
        ...
    }
};

// Or this?
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo() :
    name_(""),
    age_(0)
{
}

foo(const std::string& name, const unsigned int age) :
        name_(name),
        age_(age)
    {
        ...
    }
};

Jede Version scheint zu funktionieren, z. B.:

foo f1;
foo f2("Name", 30);

Welchen Stil bevorzugen Sie oder empfehlen Sie und warum?

War es hilfreich?

Lösung

Auf jeden Fall eine Frage des Stils. Ich bevorzuge Konstruktoren mit Standardparametern, solange die Parameter sinnvoll sind. Klassen im Standard verwenden sie ebenfalls, was zu ihren Gunsten spricht.

Eine Sache, auf die Sie achten sollten, ist, wenn Sie Standardeinstellungen für alle bis auf einen Parameter haben, kann Ihre Klasse implizit aus diesem Parametertyp konvertiert werden. Kasse Dieser Thread Für mehr Information.

Andere Tipps

Ich würde mich mit den Standardargumenten entscheiden, zumal C ++ nicht zulässt, dass Sie Konstruktoren ketten (daher müssen Sie die Initialiserliste und möglicherweise mehr für jede Überladung duplizieren).

Trotzdem gibt es einige Gotchas mit Standardargumenten, einschließlich der Tatsache, dass Konstanten eingebaut werden können (und damit Teil der binären Schnittstelle Ihrer Klasse werden). Ein weiterer Teil, auf das Sie achten müssen, ist, dass das Hinzufügen von Standardargumenten einen expliziten Multi-Argument-Konstruktor in einen impliziten Ein-Argument-Konstruktor verwandeln kann:

class Vehicle {
public:
  Vehicle(int wheels, std::string name = "Mini");
};

Vehicle x = 5;  // this compiles just fine... did you really want it to?

Diese Diskussion gilt sowohl für Konstruktoren als auch für Methoden und Funktionen.

Standardparameter verwenden?

Das Gute ist, dass Sie für jeden Fall keine Konstrukteure/Methoden/Funktionen überlasten müssen:

// Header
void doSomething(int i = 25) ;

// Source
void doSomething(int i)
{
   // Do something with i
}

Das Schlechte ist, dass Sie Ihre Standardeinstellung im Header deklarieren müssen, also haben Sie eine versteckte Abhängigkeit: Wenn Sie den Code einer eingeführten Funktion ändern, müssen Sie alle Quellen neu kompilieren, wenn Sie den Standardwert in Ihrem Header ändern müssen Verwenden Sie diesen Header, um sicherzustellen, dass sie den neuen Standard verwenden.

Wenn Sie dies nicht tun, verwenden die Quellen immer noch den alten Standardwert.

Verwenden von überladenen Konstruktoren/Methoden/Funktionen?

Das Gute ist, dass Sie den Standardwert in der Quelle steuern, indem Sie sich ausschließen, wie sich eine Funktion verhalten wird, wenn Ihre Funktionen nicht eingefügt werden. Zum Beispiel:

// Header
void doSomething() ;
void doSomething(int i) ;

// Source

void doSomething()
{
   doSomething(25) ;
}

void doSomething(int i)
{
   // Do something with i
}

Das Problem ist, dass Sie mehrere Konstruktoren/Methoden/Funktionen und ihre Weiterleitungen aufrechterhalten müssen.

Nach meiner Erfahrung scheinen die Standardparameter zu der Zeit cool zu sein und machen meinen Faulheitsfaktor glücklich, aber dann verwende ich die Klasse und ich bin überrascht, wenn der Standard einsetzt. Ich denke also nicht wirklich, dass es eine gute Idee ist ; Besser einen Klassennamen :: className () und dann einen Klassennamen :: init (Arglist). Nur für diese Wartbarkeitskante.

Sam's Die Antwort gibt den Grund an, dass Standardargumente eher für Konstruktoren als für Überladungen vorzuziehen sind. Ich möchte nur hinzufügen, dass C ++-0x zulässt Delegation Von einem Konstruktor zum anderen, wodurch die Notwendigkeit von Standardeinstellungen entfernt wird.

Beide Ansatz funktioniert. Wenn Sie jedoch über eine lange Liste optionaler Parameter verfügen, erstellen Sie einen Standardkonstruktor und geben Sie Ihre SET -Funktion darauf zurück. Dann ketten Sie die Siedler.

class Thingy2
{
public:
    enum Color{red,gree,blue};
    Thingy2();

    Thingy2 & color(Color);
    Color color()const;

    Thingy2 & length(double);
    double length()const;
    Thingy2 & width(double);
    double width()const;
    Thingy2 & height(double);
    double height()const;

    Thingy2 & rotationX(double);
    double rotationX()const;
    Thingy2 & rotatationY(double);
    double rotatationY()const;
    Thingy2 & rotationZ(double);
    double rotationZ()const;
}

main()
{
    // gets default rotations
    Thingy2 * foo=new Thingy2().color(ret)
        .length(1).width(4).height(9)
    // gets default color and sizes
    Thingy2 * bar=new Thingy2()
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
    // everything specified.
    Thingy2 * thing=new Thingy2().color(ret)
        .length(1).width(4).height(9)
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
}

Wenn Sie nun die Objekte konstruieren, können Sie auswählen, welche Eigenschaften Sie überschreiben möchten und welche Sie festgelegt haben, explizit benannt werden. Viel lesbarer :)

Sie müssen sich auch nicht mehr an die Reihenfolge der Argumente an den Konstruktor erinnern.

Eine weitere Sache zu beachten ist, ob die Klasse in einem Array verwendet werden kann oder nicht:

foo bar[400];

In diesem Szenario gibt es keinen Vorteil, den Standardparameter zu verwenden.

Dies würde sicherlich nicht funktionieren:

foo bar("david", 34)[400]; // NOPE

Wenn das Erstellen von Konstruktoren mit Argumenten schlecht ist (wie viele argumentieren würden), ist es noch schlechter, sie mit Standardargumenten zu machen. Ich habe kürzlich angefangen, der Meinung zu treffen, dass CTOR -Argumente schlecht sind, weil Ihre CTOR -Logik sollte so minimal wie möglich sein. Wie gehen Sie mit der Fehlerbehandlung im CTOR um, sollte jemand ein Argument geben, das keinen Sinn ergibt? Sie können entweder eine Ausnahme auswerfen, was schlechte Nachrichten darstellt, es sei denn, alle Ihre Anrufer sind bereit, "neue" Anrufe innerhalb von Versuchsblöcken zu wickeln oder einige "is-initialisierte" Mitgliedsvariable einzustellen, was eine Art schmutziger Hack ist.

Der einzige Weg, um sicherzustellen, dass die Argumente, die in die Initialisierungsstufe Ihres Objekts übergeben wurden, besteht darin, eine separate Initialize () -Methode einzurichten, bei der Sie den Rückgaberocode überprüfen können.

Die Verwendung von Standardargumenten ist aus zwei Gründen schlecht; Wenn Sie dem CTOR zuerst ein weiteres Argument hinzufügen möchten, stecken Sie es am Anfang fest und ändern die gesamte API. Darüber hinaus sind die meisten Programmierer daran gewöhnt, durch die Art und Weise, wie sie in der Praxis verwendet wird, eine API herauszufinden - das ist besonders Richtig für nicht öffentliche APIs, die in einer Organisation verwendet werden, in der möglicherweise keine formale Dokumentation vorliegt. Wenn andere Programmierer sehen, dass die Mehrheit der Anrufe keine Argumente enthält, werden sie dasselbe tun und sich des Standardverhaltens, das Ihre Standardargumente auferlegen, unglaublich nicht bewusst.

Es ist auch erwähnenswert, dass die Google C ++ Style Guide meidet beide CTOR -Argumente (es sei denn, dies ist absolut notwendig) und Standardargumente für Funktionen oder Methoden.

Aus diesem Grund würde ich mit den Standardparametern gehen: Ihr Beispiel wird davon ausgegangen, dass die CTOR -Parameter direkt den Mitgliedsvariablen entsprechen. Aber was ist, wenn dies nicht der Fall ist und Sie die Parameter verarbeiten müssen, bevor das Objekt initialisiert wird. Ein gemeinsamer CTOR wäre der beste Weg.

Eine Sache, die mich mit Standardparametern stört, ist, dass Sie die letzten Parameter nicht angeben können, sondern die Standardwerte für die ersten verwenden können. In Ihrem Code können Sie beispielsweise kein Foo ohne Namen erstellen, sondern ein bestimmtes Alter (wenn ich mich jedoch richtig erinnere, ist dies in C ++ 0x mit der einheitlichen Konstruktionssyntax möglich). Manchmal macht das Sinn, aber es kann auch sehr unangenehm sein.

Meiner Meinung nach gibt es keine Faustregel. Personnaly, ich neige dazu, mehrere überlastete Konstruktoren (oder Methoden) zu verwenden, außer wenn nur das letzte Argument einen Standardwert benötigt.

Meistens persönliche Wahl. Überladung kann jedoch alles tun, was den Standardparameter kann, aber nicht und umgekehrt.

Beispiel:

Sie können überladend zum Schreiben von A (int x, foo & a) und a (int x) verwenden, aber Sie können nicht den Standardparameter verwenden, um a (int x, foo & = null) zu schreiben.

Die allgemeine Regel ist es, alles zu verwenden, was Sinn macht und den Code lesbarer macht.

Frage des Stils, aber wie Matt sagte, sollten Sie auf jeden Fall Konstruktoren mit Standardargumenten markieren, die eine implizite Konvertierung als „explizit“ ermöglichen, um eine unbeabsichtigte automatische Konvertierung zu vermeiden. Es ist keine Anforderung (und ist möglicherweise nicht vorzuziehen, wenn Sie eine Wrapper -Klasse erstellen, in die Sie implizit konvertieren möchten), kann jedoch Fehler verhindern.

Ich persönlich mag es, dass ich gegebenenfalls Standardeinstellungen mag, weil ich wiederholten Code nicht mag. Ymmv.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top