Warum explizit einen Konstruktor in C++ aufrufen

Lesezeit: 7 Minuten

Benutzer-Avatar
Arnkrishn

Ich weiß, dass wir den Konstruktor einer Klasse in C++ explizit mit dem Bereichsauflösungsoperator aufrufen können, dh className::className(). Ich habe mich gefragt, wo genau ich einen solchen Anruf tätigen müsste.

  • Es ist nicht richtig zu sagen, dass Sie den Konstruktor direkt aufrufen können. Der Standard hat ausdrücklich (12.1/1): “Konstruktoren haben keine Namen.” Sie können den Konstruktor nur über andere Konstrukte aufrufen, z. B. eine Funktionsstilumwandlung oder eine neue Platzierung.

    – Richard Corden

    6. Mai 2009 um 12:37 Uhr

Benutzer-Avatar
Charlie

Sie verwenden manchmal auch explizit einen Konstruktor, um eine temporäre Datei zu erstellen. Wenn Sie beispielsweise eine Klasse mit einem Konstruktor haben:

class Foo
{
    Foo(char* c, int i);
};

und eine Funktion

void Bar(Foo foo);

aber du hast keinen Foo in der Nähe, du könntest es tun

Bar(Foo("hello", 5));

Das ist wie ein Guss. Wenn Sie einen Konstruktor haben, der nur einen Parameter akzeptiert, verwendet der C++-Compiler diesen Konstruktor, um implizite Umwandlungen durchzuführen.

es ist nicht zulässig, einen Konstruktor für ein bereits vorhandenes Objekt aufzurufen. Das heißt, Sie können nicht

Foo foo;
foo.Foo();  // compile error!

egal was du tust. Aber Sie können einen Konstruktor aufrufen, ohne Speicher zuzuweisen – das ist was Platzierung neu ist für.

char buffer[sizeof(Foo)];      // a bit of memory
Foo* foo = new(buffer) Foo();  // construct a Foo inside buffer

Sie geben new etwas Speicher und es konstruiert das Objekt an dieser Stelle, anstatt neuen Speicher zuzuweisen. Diese Verwendung wird als böse angesehen und ist in den meisten Codetypen selten, aber in eingebettetem und Datenstrukturcode üblich.

Zum Beispiel, std::vector::push_back verwendet diese Technik, um den Kopierkonstruktor aufzurufen. Auf diese Weise muss nur eine Kopie erstellt werden, anstatt ein leeres Objekt zu erstellen und den Zuweisungsoperator zu verwenden.

  • +1 für Platzierung neu. Es ist seltsam, kann aber nützlich sein, wenn Sie wissen, was Sie tun.

    – Graem Perrow

    6. Mai 2009 um 1:12 Uhr

  • “Wenn Sie einen Konstruktor haben, der nur einen Parameter akzeptiert, verwendet der C++-Compiler diesen Konstruktor, um implizite Umwandlungen durchzuführen.” – Aus diesem Grund setzen viele Leute das Schlüsselwort “explicit” standardmäßig auf Konstruktoren mit einem Argument und entfernen es nur, wenn sie sicher sind, dass sie eine implizite Umwandlung vom Parametertyp in den Klassentyp wünschen.

    – Steve Jessop

    6. Mai 2009 um 11:32 Uhr

  • -1 Sie rufen den Konstruktor nicht auf. Die Syntax ist <typename>(ctor-arg list)und pedantisch zu sein, das ist nicht dasselbe wie <typename>::<typename>(ctor-arg list). Die Syntax lässt es nur so aussehen, als würden Sie den Konstruktor aufrufen. Eigentlich du noch nie “rufen” Sie einen Konstruktor wie eine Funktion auf.

    – Leichtigkeitsrennen im Orbit

    14. März 2011 um 14:54 Uhr

  • @LightnessRacesinOrbit Was passiert eigentlich, wenn du es tust <typename>::<typename> <var-name>(ctor-arg list) dann?

    Benutzer1599559

    28. April 2013 um 4:09 Uhr

  • @AdamSchnitzer: Viele Dinge, einschließlich Speicherzuweisung, Basisinitialisierung und Mitgliederinitialisierung.

    – Leichtigkeitsrennen im Orbit

    28. April 2013 um 15:01 Uhr


Benutzer-Avatar
Anspruch

Meistens in einem untergeordneten Klassenkonstruktor, der einige Parameter erfordert:

class BaseClass
{
public:
    BaseClass( const std::string& name ) : m_name( name ) { }

    const std::string& getName() const { return m_name; }

private:

    const std::string m_name;

//...

};


class DerivedClass : public BaseClass
{
public:

    DerivedClass( const std::string& name ) : BaseClass( name ) { }

// ...
};

class TestClass : 
{
public:
    TestClass( int testValue ); //...
};

class UniqueTestClass 
     : public BaseClass
     , public TestClass
{
public:
    UniqueTestClass() 
       : BaseClass( "UniqueTest" ) 
       , TestClass( 42 )
    { }

// ...
};

… zum Beispiel.

Ansonsten sehe ich den Nutzen nicht. Ich habe den Konstruktor in anderem Code nur aufgerufen, als ich zu jung war, um zu wissen, was ich wirklich tat …

  • C++ ruft implizit den Konstruktor von übergeordneten Klassen auf, wenn eine Instanz einer abgeleiteten Klasse erstellt wird, aber es ruft den Standardkonstruktor auf – es sei denn, Sie rufen selbst explizit einen bestimmten Konstruktor der übergeordneten Klasse in der Initialisiererliste auf.

    – sean riley

    6. Mai 2009 um 0:59 Uhr

  • Ja, in meinem Beispiel habe ich dafür gesorgt, dass der einzig gültige Konstruktor für BaseClass einige Parameter benötigt. Ich erinnere mich nicht an einen Fall, der wirklich einen expliziten Standardkonstruktoraufruf erforderte. Vielleicht im virtuellen Erbe?

    – Anspruch

    6. Mai 2009 um 1:03 Uhr

Ich denke, die Fehlermeldung für den Compilerfehler C2585 gibt den besten Grund, warum Sie den Bereichsauflösungsoperator tatsächlich für den Konstruktor verwenden müssten, und das stimmt mit Charlies Antwort überein:

Konvertieren von einem Klassen- oder Strukturtyp basierend auf Mehrfachvererbung. Wenn der Typ dieselbe Basisklasse mehr als einmal erbt, muss die Konvertierungsfunktion oder der Konvertierungsoperator die Bereichsauflösung (::) verwenden, um anzugeben, welche der geerbten Klassen bei der Konvertierung verwendet werden sollen.

Stellen Sie sich also vor, Sie haben BaseClass, und BaseClassA und BaseClassB erben beide BaseClass, und dann erbt DerivedClass sowohl BaseClassA als auch BaseClassB.

Wenn Sie eine Konvertierung oder Operatorüberladung durchführen, um DerivedClass in eine BaseClassA oder BaseClassB zu konvertieren, müssen Sie angeben, welcher Konstruktor (ich denke an so etwas wie einen Kopierkonstruktor, IIRC) für die Konvertierung verwendet werden soll.

Im Allgemeinen rufen Sie den Konstruktor nicht direkt auf. Der new-Operator ruft ihn für Sie auf, oder eine Unterklasse ruft die Konstruktoren der übergeordneten Klasse auf. In C++ wird garantiert, dass die Basisklasse vollständig konstruiert ist, bevor der Konstruktor der abgeleiteten Klasse beginnt.

Das einzige Mal, dass Sie einen Konstruktor direkt aufrufen würden, ist der äußerst seltene Fall, in dem Sie Speicher verwalten, ohne new zu verwenden. Und selbst dann solltest du es nicht tun. Stattdessen sollten Sie das Platzierungsformular von operator new verwenden.

Ich glaube nicht, dass Sie das normalerweise für den Konstruktor verwenden würden, zumindest nicht so, wie Sie es beschreiben. Sie würden es jedoch benötigen, wenn Sie zwei Klassen in unterschiedlichen Namespaces haben. Um beispielsweise den Unterschied zwischen diesen beiden zusammengesetzten Klassen anzugeben, Xml::Element und Chemistry::Element.

Normalerweise wird der Name der Klasse mit dem Bereichsauflösungsoperator verwendet, um eine Funktion für die übergeordnete Klasse einer geerbten Klasse aufzurufen. Wenn Sie also eine Klasse Dog haben, die von Animal erbt, und diese beiden Klassen die Funktion Eat() unterschiedlich definieren, kann es vorkommen, dass Sie die Animal-Version von eat für ein Dog-Objekt namens “someDog” verwenden möchten. Meine C++-Syntax ist ein wenig eingerostet, aber ich denke, in diesem Fall würden Sie sagen someDog.Animal::Eat().

Benutzer-Avatar
jls

Es gibt gültige Anwendungsfälle, in denen Sie Klassenkonstruktoren verfügbar machen möchten. Wenn Sie beispielsweise mit einem Arena-Allokator Ihre eigene Speicherverwaltung durchführen möchten, benötigen Sie einen zweiphasigen Aufbau, bestehend aus Allokation und Objektinitialisierung.

Meine Herangehensweise ist ähnlich wie bei vielen anderen Sprachen. Ich habe einfach meinen Konstruktionscode in bekannte öffentliche Methoden (Konstruieren(), drin()so ähnlich) und rufe sie bei Bedarf direkt an.

Sie können Überladungen dieser Methoden erstellen, die Ihren Konstruktoren entsprechen. Ihre regulären Konstruktoren rufen sie einfach auf. Fügen Sie große Kommentare in den Code ein, um andere zu warnen, dass Sie dies tun, damit sie keinen wichtigen Konstruktionscode an der falschen Stelle hinzufügen.

Denken Sie daran, dass es nur eine Destruktormethode gibt, unabhängig davon, welche Konstruktionsüberladung verwendet wurde, also machen Sie Ihre Destruktoren robust gegenüber nicht initialisierten Membern.

Ich rate davon ab, Initialisierer zu schreiben, die neu initialisiert werden können. Es ist schwer zu sagen, ob Sie ein Objekt betrachten, das nur Müll enthält, weil der Speicher nicht initialisiert ist oder tatsächlich echte Daten enthält.

Das schwierigste Problem sind Klassen mit virtuellen Methoden. In diesem Fall fügt der Compiler normalerweise den Tabellenzeiger der vtable-Funktion als verstecktes Feld am Anfang der Klasse ein. Sie können diesen Zeiger manuell initialisieren, aber Sie sind grundsätzlich vom Compiler-spezifischen Verhalten abhängig, und es ist wahrscheinlich, dass Ihre Kollegen Sie komisch angucken.

Platzierung neu ist in vielerlei Hinsicht gebrochen; in der Konstruktion/Zerstörung von Arrays ist ein Fall, also neige ich dazu, es nicht zu verwenden.

Benutzer-Avatar
Devesh Agrawal

Betrachten Sie das folgende Programm.

template<class T>
double GetAverage(T tArray[], int nElements)
{
T tSum = T(); // tSum = 0

for (int nIndex = 0; nIndex < nElements; ++nIndex)
{
    tSum += tArray[nIndex];
}

// Whatever type of T is, convert to double
return double(tSum) / nElements;
}

Dadurch wird explizit ein Standardkonstruktor aufgerufen, um die Variable zu initialisieren.

1015550cookie-checkWarum explizit einen Konstruktor in C++ aufrufen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy