webentwicklung-frage-antwort-db.com.de

Kann ich einen Konstruktor von einem anderen Konstruktor in C ++ aufrufen (Konstruktor verketten)?

Als C # Entwickler bin ich es gewohnt, Konstruktoren zu durchlaufen:

class Test {
    public Test() {
        DoSomething();
    }

    public Test(int count) : this() {
        DoSomethingWithCount(count);
    }

    public Test(int count, string name) : this(count) {
        DoSomethingWithName(name);
    }
}

Gibt es eine Möglichkeit, dies in C++ zu tun?

Ich habe versucht, den Klassennamen aufzurufen und das Schlüsselwort 'this' zu verwenden, aber beides schlägt fehl.

856
Stormenet

C++ 11: Ja!

C++ 11 und höher verfügt über dieselbe Funktion ( delegierende Konstruktoren ).

Die Syntax unterscheidet sich geringfügig von C #:

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {}
};

C++ 03: Nein

Leider gibt es in C++ 03 keine Möglichkeit, dies zu tun, aber es gibt zwei Möglichkeiten, dies zu simulieren:

  1. Sie können zwei (oder mehr) Konstruktoren über Standardparameter kombinieren:

    class Foo {
    public:
      Foo(char x, int y=0);  // combines two constructors (char) and (char, int)
      // ...
    };
    
  2. Verwenden Sie eine Init-Methode, um gemeinsamen Code freizugeben:

    class Foo {
    public:
      Foo(char x);
      Foo(char x, int y);
      // ...
    private:
      void init(char x, int y);
    };
    
    Foo::Foo(char x)
    {
      init(x, int(x) + 7);
      // ...
    }
    
    Foo::Foo(char x, int y)
    {
      init(x, y);
      // ...
    }
    
    void Foo::init(char x, int y)
    {
      // ...
    }
    

Siehe der C++ - FAQ-Eintrag als Referenz.

1148
JohnIdol

Nein, Sie können einen Konstruktor in C++ 03 nicht von einem anderen Konstruktor aufrufen (der als delegierender Konstruktor bezeichnet wird).

Dies änderte sich in C++ 11 (auch bekannt als C++ 0x), das die Unterstützung der folgenden Syntax hinzufügte:
(Beispiel aus Wikipedia )

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};
108
Cyrille Ka

Ich glaube, Sie können einen Konstruktor von einem Konstruktor aufrufen. Es wird kompiliert und ausgeführt. Ich habe kürzlich jemanden gesehen, der dies getan hat, und es lief sowohl unter Windows als auch unter Linux.

Es macht einfach nicht, was Sie wollen. Der innere Konstruktor erstellt ein temporäres lokales Objekt, das gelöscht wird, sobald der äußere Konstruktor zurückkehrt. Es müssten auch andere Konstruktoren sein, oder Sie erstellen einen rekursiven Aufruf.

Ref: https://isocpp.org/wiki/faq/ctors#init-methods

39
ohlemacher

Es sei darauf hingewiesen, dass Sie can den Konstruktor einer übergeordneten Klasse in Ihrem Konstruktor aufrufen, z.

class A { /* ... */ };

class B : public A
{
    B() : A()
    {
        // ...
    }
};

Aber nein, Sie können keinen anderen Konstruktor derselben Klasse aufrufen.

22
kchoose2

In C++ 11 kann ein Konstruktor eine andere Konstruktorüberladung aufrufen :

class Foo  {
     int d;         
public:
    Foo  (int i) : d(i) {}
    Foo  () : Foo(42) {} //New to C++11
};

Zusätzlich können auch Mitglieder auf diese Weise initialisiert werden.

class Foo  {
     int d = 5;         
public:
    Foo  (int i) : d(i) {}
};

Dadurch muss die Initialisierungshilfemethode nicht mehr erstellt werden. Es wird weiterhin empfohlen, keine virtuellen Funktionen in den Konstruktoren oder Destruktoren aufzurufen, um die Verwendung von Elementen zu vermeiden, die möglicherweise nicht initialisiert wurden.

18
Ben L

Wenn Sie böse sein wollen, können Sie den vorhandenen "neuen" Operator verwenden:

class Foo() {
    Foo() { /* default constructor deliciousness */ }
    Foo(Bar myParam) {
      new (this) Foo();
      /* bar your param all night long */
    } 
};

Scheint für mich zu arbeiten.

bearbeiten

Wie @ElvedinHamzagic betont, wird dieses Objekt möglicherweise nicht freigegeben, wenn Foo ein Objekt enthält, das Speicher zuweist. Dies erschwert die Sache weiter.

Ein allgemeineres Beispiel:

class Foo() {
private:
  std::vector<int> Stuff;
public:
    Foo()
      : Stuff(42)
    {
      /* default constructor deliciousness */
    }

    Foo(Bar myParam)
    {
      this->~Foo();
      new (this) Foo();
      /* bar your param all night long */
    } 
};

Sieht sicher etwas weniger elegant aus. @ JohnIdols Lösung ist viel besser.

12
lyngvi

Nein, in C++ können Sie keinen Konstruktor von einem Konstruktor aus aufrufen. Was Sie tun können, wie Warren betonte, ist:

  • Überladen Sie den Konstruktor mit verschiedenen Signaturen
  • Verwenden Sie Standardwerte für Argumente, um eine "einfachere" Version verfügbar zu machen

Beachten Sie, dass Sie im ersten Fall die Codeduplizierung nicht reduzieren können, indem Sie einen Konstruktor von einem anderen aufrufen. Sie können natürlich eine separate, private/protected-Methode verwenden, die die gesamte Initialisierung durchführt, und den Konstruktor hauptsächlich mit der Argumentbehandlung befassen lassen.

8
unwind

Einfach ausgedrückt, können Sie nicht vor C++ 11.

In C++ 11 werden delegierende Konstruktoren eingeführt :

Konstruktor delegieren

Wenn der Name der Klasse selbst als Klasse oder Kennung in der Liste der Elementinitialisierer angezeigt wird, darf die Liste nur aus diesem einen Elementinitialisierer bestehen. Ein solcher Konstruktor ist als delegierender Konstruktor bekannt, und der Konstruktor, der vom einzigen Mitglied der Initialisierungsliste ausgewählt wird, ist der Zielkonstruktor

In diesem Fall wird der Zielkonstruktor durch Überladungsauflösung ausgewählt und zuerst ausgeführt, dann kehrt das Steuerelement zum delegierenden Konstruktor zurück und sein Hauptteil wird ausgeführt.

Konstruktoren können nicht rekursiv delegiert werden.

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
};

Beachten Sie, dass ein delegierender Konstruktor ein Alles-oder-Nichts-Vorschlag ist. Wenn ein Konstruktor an einen anderen Konstruktor delegiert, darf der aufrufende Konstruktor keine anderen Mitglieder in seiner Initialisierungsliste haben. Dies ist sinnvoll, wenn Sie darüber nachdenken, const/reference-Member nur einmal zu initialisieren.

4
user8683714

In Visual C++ können Sie diese Notation auch innerhalb des Konstruktors verwenden: this-> Classname :: Classname (Parameter eines anderen Konstruktors). Siehe ein Beispiel unten:

class Vertex
{
 private:
  int x, y;
 public:
  Vertex(int xCoo, int yCoo): x(xCoo), y(yCoo) {}
  Vertex()
  {
   this->Vertex::Vertex(-1, -1);
  }
};

Ich weiß nicht, ob es irgendwo anders funktioniert, ich habe es nur in Visual C++ 2003 und 2008 getestet. Sie können auch mehrere Konstruktoren auf diese Weise aufrufen, nehme ich an, genau wie in Java und C#.

P .: Ehrlich gesagt war ich überrascht, dass dies zuvor nicht erwähnt wurde.

4
izogfif

Eine weitere Option, die noch nicht gezeigt wurde, besteht darin, Ihre Klasse in zwei Teile zu unterteilen und eine einfache Schnittstellenklasse um Ihre ursprüngliche Klasse zu wickeln, um den gewünschten Effekt zu erzielen:

class Test_Base {
    public Test_Base() {
        DoSomething();
    }
};

class Test : public Test_Base {
    public Test() : Test_Base() {
    }

    public Test(int count) : Test_Base() {
        DoSomethingWithCount(count);
    }
};

Dies kann problematisch werden, wenn Sie viele Konstruktoren haben, die ihr Gegenstück "next level up" aufrufen müssen. Für eine Handvoll Konstruktoren sollte dies jedoch praktikabel sein.

3
e.James

Ich würde die Verwendung einer private friend -Methode vorschlagen, die die Anwendungslogik des Konstruktors implementiert und von den verschiedenen Konstruktoren aufgerufen wird. Hier ist ein Beispiel:

Angenommen, wir haben eine Klasse namens StreamArrayReader mit einigen privaten Feldern:

private:
    istream * in;
      // More private fields

Und wir wollen die beiden Konstruktoren definieren:

public:
    StreamArrayReader(istream * in_stream);
    StreamArrayReader(char * filepath);
    // More constructors...

Wobei der zweite einfach den ersten benutzt (und natürlich wollen wir die Implementierung des ersteren nicht duplizieren). Idealerweise möchte man etwas machen wie:

StreamArrayReader::StreamArrayReader(istream * in_stream){
    // Implementation
}

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    StreamArrayReader(&instream);
    instream.close();
}

Dies ist jedoch in C++ nicht zulässig. Aus diesem Grund können wir eine private friend-Methode wie folgt definieren, die implementiert, was der erste Konstruktor tun soll:

private:
  friend void init_stream_array_reader(StreamArrayReader *o, istream * is);

Jetzt hat diese Methode (weil es ein Freund ist) Zugriff auf die privaten Felder von o. Dann wird der erste Konstruktor:

StreamArrayReader::StreamArrayReader(istream * is) {
    init_stream_array_reader(this, is);
}

Beachten Sie, dass dadurch nicht mehrere Kopien für die neu erstellten Kopien erstellt werden. Der zweite wird:

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    init_stream_array_reader(this, &instream);
    instream.close();
}

Das heißt, anstatt dass ein Konstruktor einen anderen aufruft, rufen beide einen privaten Freund auf!

2

Dieser Ansatz funktioniert möglicherweise für einige Arten von Klassen (wenn sich der Zuweisungsoperator "gut" verhält):

Foo::Foo()
{
    // do what every Foo is needing
    ...
}

Foo::Foo(char x)
{
    *this = Foo();

    // do the special things for a Foo with char
    ...
}
2
V15I0N

Wenn ich Ihre Frage richtig verstehe, fragen Sie, ob Sie in C++ mehrere Konstruktoren aufrufen können?

Wenn Sie danach suchen, nein, das ist nicht möglich.

Sie können durchaus mehrere Konstruktoren mit jeweils eindeutigen Argument-Signaturen haben und dann den gewünschten aufrufen, wenn Sie ein neues Objekt instanziieren.

Sie können sogar einen Konstruktor mit Standardargumenten am Ende haben.

Möglicherweise verfügen Sie jedoch nicht über mehrere Konstruktoren, und rufen Sie sie dann einzeln auf.

1
warren

Beim Aufrufen eines Konstruktors wird tatsächlich Speicher zugewiesen, entweder vom Stapel oder vom Heap. Wenn Sie also einen Konstruktor in einem anderen Konstruktor aufrufen, wird eine lokale Kopie erstellt. Also modifizieren wir ein anderes Objekt, nicht das, auf das wir uns konzentrieren.

0
XPD

Wäre einfacher zu testen, als zu entscheiden :) Versuchen Sie Folgendes:

#include <iostream>

class A {
public:
    A( int a) : m_a(a) {
        std::cout << "A::Ctor" << std::endl;    
    }
    ~A() {
        std::cout << "A::dtor" << std::endl;    
    }
public:
    int m_a;
};

class B : public A {
public:
    B( int a, int b) : m_b(b), A(a) {}
public:
    int m_b;
};

int main() {
    B b(9, 6);
    std::cout << "Test constructor delegation a = " << b.m_a << "; b = " << b.m_b << std::endl;    
    return 0;
}

und kompiliere es mit 98 std: g ++ main.cpp -std = c ++ 98 -o test_1

du wirst sehen:

A::Ctor
Test constructor delegation a = 9; b = 6
A::dtor

damit :)

0
gyula