wake-up-neo.com

Warum explizit einen Konstruktor in C++ aufrufen

Ich weiß, dass wir den Konstruktor einer Klasse in C++ mithilfe des Bereichsauflösungsoperators explizit aufrufen können, d. H. className::className(). Ich fragte mich, wo genau ich einen solchen Anruf machen müsste.

21
Arnkrishn

Meistens in einem untergeordneten Klassenkonstruktor, für den einige Parameter erforderlich sind:

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.

Abgesehen davon sehe ich das Dienstprogramm nicht. Ich habe den Konstruktor erst in einem anderen Code aufgerufen, als ich noch zu jung war, um zu wissen, was ich wirklich tat ...

13
Klaim

Manchmal verwenden Sie auch explizit einen Konstruktor, um temporäre Objekte 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 Sie haben kein Foo in der Nähe, das könnten Sie tun

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

Das ist wie eine Besetzung. Wenn Sie über einen Konstruktor verfügen, der nur einen Parameter verwendet, verwendet der C++ - Compiler diesen Konstruktor, um implizite Umsetzungen durchzuführen.

Es ist nicht legal, einen Konstruktor für ein bereits vorhandenes Objekt aufzurufen. Das kann man nicht tun

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

egal was du tust. Sie können jedoch einen Konstruktor aufrufen, ohne Speicher zuzuordnen. Dafür gibt es placement new .

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

Sie geben neuem Speicher etwas Zeit und errichten das Objekt an dieser Stelle, anstatt neuen Speicher zuzuweisen. Diese Verwendung wird als böse angesehen und ist bei den meisten Codetypen selten, jedoch bei eingebettetem Code und Datenstrukturcode üblich. 

Beispielsweise verwendet std::vector::Push_back 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.

44
Charlie

Ich denke, die Fehlermeldung für den Compilerfehler C2585 gibt den besten Grund an, warum Sie tatsächlich den Bereichsauflösungsoperator für den Konstruktor verwenden müssen, und dies tut Charlies Antwort:

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

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

Wenn Sie eine Konvertierung oder eine Überladung des Operators durchführen, um DerivedClass in eine BaseClassA oder BaseClassB zu konvertieren, müssen Sie ermitteln, welcher Konstruktor (ich denke so etwas wie einen Kopierkonstruktor, IIRC), der bei der Konvertierung verwendet werden soll.

3

Im Allgemeinen rufen Sie den Konstruktor nicht direkt auf. Der neue Operator ruft es für Sie auf, oder eine Unterklasse ruft die Konstruktoren der übergeordneten Klasse auf. In C++ muss die Basisklasse vor dem Start des Konstruktors der abgeleiteten Klasse vollständig erstellt werden.

Der einzige Zeitpunkt, an dem Sie einen Konstruktor direkt aufrufen würden, ist der äußerst seltene Fall, in dem Sie Speicher verwalten, ohne neuen zu verwenden. Und selbst dann sollten Sie es nicht tun. Verwenden Sie stattdessen das Platzierungsformular für Operator new.

2
jmucchiello

Es gibt gültige Anwendungsfälle, in denen Sie Klassenkonstruktoren verfügbar machen möchten. Wenn Sie beispielsweise Ihre eigene Speicherverwaltung mit einem Arena-Zuweiser durchführen möchten, benötigen Sie eine zweiphasige Konstruktion, die aus der Zuweisung und der Objektinitialisierung besteht. 

Mein Ansatz ist ähnlich wie in vielen anderen Sprachen. Ich gebe meinen Konstruktionscode einfach in bekannte öffentliche Methoden ( Construct () , init () , sowas) und rufe sie bei Bedarf direkt an. 

Sie können Überladungen dieser Methoden erstellen, die Ihren Konstruktoren entsprechen. Ihre regulären Konstrukteure rufen sie einfach an. Fügen Sie große Kommentare in den Code ein, um andere darauf hinzuweisen, dass Sie dies tun, damit wichtige Konstruktionscodes nicht an der falschen Stelle eingefügt werden.

Denken Sie daran, dass es nur eine einzige Destruktor-Methode gibt, unabhängig davon, welche Konstruktionsüberladung verwendet wurde. Machen Sie daher Ihre Destruktoren robust für nicht initialisierte Mitglieder. 

Ich empfehle, nicht zu versuchen, Initialisierer zu schreiben, die sich neu initialisieren lassen. Es ist schwer zu sagen, in welchem ​​Fall Sie ein Objekt betrachten, in dem nur Speicherplatz vorhanden ist, weil nicht initialisierter Speicher vorhanden ist oder Sie tatsächlich Daten speichern.

Das schwierigste Problem betrifft Klassen mit virtuellen Methoden. In diesem Fall fügt der Compiler den vtable-Funktionstabellenzeiger normalerweise als verstecktes Feld am Anfang der Klasse ein. Sie können diesen Zeiger manuell initialisieren, aber Sie hängen im Wesentlichen von dem spezifischen Verhalten des Compilers ab und es ist wahrscheinlich, dass Ihre Kollegen Sie komisch betrachten. 

Platzierung neu ist in vielerlei Hinsicht gebrochen; Bei der Konstruktion/Zerstörung von Arrays handelt es sich um einen Fall, daher neige ich dazu, ihn nicht zu verwenden.

0
jls

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 beide 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 etwas verrostet, aber ich denke in diesem Fall würde man someDog.Animal::Eat() sagen.

0

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 ein Standardkonstruktor explizit aufgerufen, um die Variable zu initialisieren.

0
Devesh Agrawal