webentwicklung-frage-antwort-db.com.de

Standardparameter mit C ++ - Konstruktoren

Ist es empfehlenswert, einen Klassenkonstruktor zu haben, der Standardparameter verwendet, oder sollte ich separate überladene Konstruktoren verwenden? Beispielsweise:

// 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)
    {
        ...
    }
};

Beide Versionen scheinen zu funktionieren, z.

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

Welchen Stil bevorzugen oder empfehlen Sie und warum?

108
Rob

Auf jeden Fall eine Frage des Stils. Ich bevorzuge Konstruktoren mit Standardparametern, solange die Parameter sinnvoll sind. Klassen in der Norm verwenden sie auch, was zu ihren Gunsten spricht.

Eine Sache, auf die Sie achten müssen, ist, dass Ihre Klasse implizit aus diesem Parametertyp konvertiert werden kann, wenn Sie Standardeinstellungen für alle bis auf einen Parameter haben. Check out dieser Thread für weitere Informationen.

84
luke

Ich würde mich für die Standardargumente entscheiden, zumal Sie in C++ keine Konstruktoren verketten können (sodass Sie die Initialisierungsliste und möglicherweise mehr für jede Überladung duplizieren müssen).

Das heißt, es gibt einige Fallstricke mit Standardargumenten, einschließlich der Tatsache, dass Konstanten inline sein können (und dadurch Teil der Binärschnittstelle Ihrer Klasse werden). Beachten Sie außerdem, dass das Hinzufügen von Standardargumenten einen expliziten Konstruktor mit mehreren Argumenten in einen impliziten Konstruktor mit einem Argument 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?
35
Sam Stokes

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

Standardparameter verwenden?

Das Gute ist, dass Sie nicht für jeden Fall Konstruktoren/Methoden/Funktionen überladen müssen:

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

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

Das Schlimme ist, dass Sie Ihre Standardeinstellung in der Kopfzeile deklarieren müssen, so dass Sie eine versteckte Abhängigkeit haben: Wenn Sie den Code einer eingebetteten Funktion ändern, müssen Sie alle Quellen neu kompilieren, wenn Sie den Standardwert in Ihrer Kopfzeile ändern Verwenden Sie diesen Header, um sicherzustellen, dass die neuen Standardeinstellungen verwendet werden.

Andernfalls verwenden die Quellen weiterhin den alten Standardwert.

verwenden Sie überladene Konstruktoren/Methoden/Funktionen?

Das Gute daran ist, dass Sie, wenn Ihre Funktionen nicht inline sind, den Standardwert in der Quelle steuern, indem Sie auswählen, wie sich eine Funktion verhält. Beispielsweise:

// 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 deren Weiterleitungen verwalten müssen.

15
paercebal

Nach meiner Erfahrung scheinen Standardparameter zu der Zeit cool zu sein und meinen Faulheitsfaktor glücklich zu machen, aber dann benutze ich die Klasse und bin überrascht, wenn der Standard einsetzt. Ich halte das also nicht wirklich für eine gute Idee ; Besser einen className :: className () und dann einen className :: init (arglist). Nur für diese Wartbarkeit Edge.

13
Paul Nathan

Sams Antwort gibt den Grund an, warum Standardargumente für Konstruktoren vorzuziehen sind, anstatt sie zu überladen. Ich möchte nur hinzufügen, dass C++ - 0x Delegierung von einem Konstruktor zu einem anderen zulässt, wodurch die Notwendigkeit für Standardeinstellungen entfällt.

7
Richard Corden

Beide Ansätze funktionieren. Wenn Sie jedoch eine lange Liste optionaler Parameter haben, erstellen Sie einen Standardkonstruktor und lassen Sie Ihre Set-Funktion einen Verweis darauf zurückgeben. Dann kette die Siedler an.

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);
}

Jetzt können Sie beim Erstellen der Objekte auswählen, welche Eigenschaften überschrieben werden sollen und welche Sie explizit festgelegt haben. Viel besser lesbar :)

Außerdem müssen Sie sich die Reihenfolge der Argumente für den Konstruktor nicht mehr merken.

5
Rodney Schuler

Wenn das Erstellen von Konstruktoren mit Argumenten schlecht ist (wie viele argumentieren würden), ist es noch schlimmer, sie mit Standardargumenten zu erstellen. Ich bin kürzlich zu der Ansicht gelangt, dass ctor-Argumente schlecht sind, weil Ihre ctor-Logikso minimal wie möglich sein sollte. Wie gehen Sie mit der Fehlerbehandlung im ctor um, sollte jemand ein Argument übergeben, das keinen Sinn ergibt? Sie können entweder eine Ausnahme auslösen, die eine schlechte Nachricht ist, es sei denn, alle Ihre Anrufer sind bereit, "neue" Aufrufe in try-Blöcke einzuschließen, oder Sie legen eine "is-initialized" -Mitgliedsvariable fest, was eine Art Dirty-Hack ist.

Die einzige Möglichkeit, um sicherzustellen, dass die in die Initialisierungsphase Ihres Objekts übergebenen Argumente vorhanden sind, besteht darin, eine separate initialize () - Methode einzurichten, mit der Sie den Rückkehrcode überprüfen können.

Die Verwendung von Standardargumenten ist aus zwei Gründen schlecht. Zuallererst, wenn Sie dem ctor ein weiteres Argument hinzufügen möchten, müssen Sie es nicht mehr an den Anfang setzen und die gesamte API ändern. Darüber hinaus sind die meisten Programmierer daran gewöhnt, eine API anhand der Art und Weise zu ermitteln, wie sie in der Praxis verwendet wird. Dies istinsbesonderetrue für nicht öffentliche APIs, die in einer Organisation verwendet werden, in der formale Dokumentationen möglicherweise nicht existieren . Wenn andere Programmierer feststellen, dass die meisten Aufrufe keine Argumente enthalten, tun sie dasselbe und sind sich des Standardverhaltens, das Ihre Standardargumente ihnen auferlegen, nicht bewusst.

Es ist auch erwähnenswert, dass das Google C++ - Styleguide beide ctor-Argumente (sofern nicht unbedingt erforderlich) und Standardargumente für Funktionen oder Methoden meidet.

3
Nik Reiman

Eine weitere Überlegung ist, ob die Klasse in einem Array verwendet werden kann oder nicht:

foo bar[400];

In diesem Szenario hat die Verwendung des Standardparameters keinen Vorteil.

Das würde sicherlich NICHT funktionieren:

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

Ich würde aus diesem Grund mit den Standardparametern gehen: In Ihrem Beispiel wird davon ausgegangen, dass die ctor-Parameter direkt den Mitgliedsvariablen entsprechen. 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.

2
James Curran

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

Beispiel:

Sie können Überladung verwenden, um A (int x, foo & a) und A (int x) zu schreiben, aber Sie können den Standardparameter nicht verwenden, um A (int x, foo & = null) zu schreiben.

Die allgemeine Regel ist, alles zu verwenden, was Sinn macht und den Code besser lesbar macht.

2
Yang Lu

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

Meiner Meinung nach gibt es keine Faustregel. Ich benutze normalerweise mehrere überladene Konstruktoren (oder Methoden), außer wenn nur das letzte Argument einen Standardwert benötigt.

2
Luc Touraille

Eine Frage des Stils, aber wie Matt sagte, sollten Sie Konstruktoren definitiv mit Standardargumenten kennzeichnen, die eine implizite Konvertierung als "explizit" erlauben, um eine unbeabsichtigte automatische Konvertierung zu vermeiden. Dies 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 Standardeinstellungen, wenn dies angebracht ist, weil ich wiederholten Code nicht mag. YMMV.

0
Nick